diff --git a/.eslintrc.js b/.eslintrc.js index 3161a25b70870..3778bd374da61 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -17,9 +17,6 @@ * under the License. */ -const { readdirSync } = require('fs'); -const { resolve } = require('path'); - const APACHE_2_0_LICENSE_HEADER = ` /* * Licensed to Elasticsearch B.V. under one or more contributor @@ -288,7 +285,7 @@ module.exports = { }, { target: [ - '(src|x-pack)/legacy/**/*', + 'src/legacy/**/*', '(src|x-pack)/plugins/**/(public|server)/**/*', 'examples/**/*', ], @@ -319,14 +316,11 @@ module.exports = { }, { target: [ - '(src|x-pack)/legacy/**/*', + 'src/legacy/**/*', '(src|x-pack)/plugins/**/(public|server)/**/*', 'examples/**/*', '!(src|x-pack)/**/*.test.*', '!(x-pack/)?test/**/*', - // next folder contains legacy browser tests which can't be migrated to jest - // which import np files - '!src/legacy/core_plugins/kibana/public/__tests__/**/*', ], from: [ '(src|x-pack)/plugins/**/(public|server)/**/*', @@ -341,14 +335,6 @@ module.exports = { '(src|x-pack)/plugins/**/*', '!(src|x-pack)/plugins/**/server/**/*', - 'src/legacy/core_plugins/**/*', - '!src/legacy/core_plugins/**/server/**/*', - '!src/legacy/core_plugins/**/index.{js,mjs,ts,tsx}', - - 'x-pack/legacy/plugins/**/*', - '!x-pack/legacy/plugins/**/server/**/*', - '!x-pack/legacy/plugins/**/index.{js,mjs,ts,tsx}', - 'examples/**/*', '!examples/**/server/**/*', ], @@ -370,12 +356,7 @@ module.exports = { }, { target: ['src/core/**/*'], - from: [ - 'plugins/**/*', - 'src/plugins/**/*', - 'src/legacy/core_plugins/**/*', - 'src/legacy/ui/**/*', - ], + from: ['plugins/**/*', 'src/plugins/**/*', 'src/legacy/ui/**/*'], errorMessage: 'The core cannot depend on any plugins.', }, { @@ -388,12 +369,6 @@ module.exports = { target: [ 'test/plugin_functional/plugins/**/public/np_ready/**/*', 'test/plugin_functional/plugins/**/server/np_ready/**/*', - 'src/legacy/core_plugins/**/public/np_ready/**/*', - 'src/legacy/core_plugins/vis_type_*/public/**/*', - '!src/legacy/core_plugins/vis_type_*/public/legacy*', - 'src/legacy/core_plugins/**/server/np_ready/**/*', - 'x-pack/legacy/plugins/**/public/np_ready/**/*', - 'x-pack/legacy/plugins/**/server/np_ready/**/*', ], allowSameFolder: true, errorMessage: @@ -443,22 +418,14 @@ module.exports = { settings: { // instructs import/no-extraneous-dependencies to treat certain modules // as core modules, even if they aren't listed in package.json - 'import/core-modules': ['plugins', 'legacy/ui'], + 'import/core-modules': ['plugins'], 'import/resolver': { '@kbn/eslint-import-resolver-kibana': { forceNode: false, rootPackageName: 'kibana', kibanaPath: '.', - pluginMap: readdirSync(resolve(__dirname, 'x-pack/legacy/plugins')).reduce( - (acc, name) => { - if (!name.startsWith('_')) { - acc[name] = `x-pack/legacy/plugins/${name}`; - } - return acc; - }, - {} - ), + pluginMap: {}, }, }, }, @@ -764,16 +731,6 @@ module.exports = { }, }, - /** - * GIS overrides - */ - { - files: ['x-pack/legacy/plugins/maps/**/*.js'], - rules: { - 'react/prefer-stateless-function': [0, { ignorePureComponents: false }], - }, - }, - /** * ML overrides */ @@ -812,7 +769,7 @@ module.exports = { }, { // typescript only for front and back end - files: ['x-pack/{,legacy/}plugins/security_solution/**/*.{ts,tsx}'], + files: ['x-pack/plugins/security_solution/**/*.{ts,tsx}'], rules: { // This will be turned on after bug fixes are complete // '@typescript-eslint/explicit-member-accessibility': 'warn', @@ -858,7 +815,7 @@ module.exports = { // }, { // typescript and javascript for front and back end - files: ['x-pack/{,legacy/}plugins/security_solution/**/*.{js,mjs,ts,tsx}'], + files: ['x-pack/plugins/security_solution/**/*.{js,mjs,ts,tsx}'], plugins: ['eslint-plugin-node', 'react'], env: { mocha: true, @@ -1089,7 +1046,7 @@ module.exports = { { // typescript only for front and back end files: [ - 'x-pack/{,legacy/}plugins/{alerts,alerting_builtins,actions,task_manager,event_log}/**/*.{ts,tsx}', + 'x-pack/plugins/{alerts,alerting_builtins,actions,task_manager,event_log}/**/*.{ts,tsx}', ], rules: { '@typescript-eslint/no-explicit-any': 'error', @@ -1238,10 +1195,7 @@ module.exports = { * TSVB overrides */ { - files: [ - 'src/plugins/vis_type_timeseries/**/*.{js,mjs,ts,tsx}', - 'src/legacy/core_plugins/vis_type_timeseries/**/*.{js,mjs,ts,tsx}', - ], + files: ['src/plugins/vis_type_timeseries/**/*.{js,mjs,ts,tsx}'], rules: { 'import/no-default-export': 'error', }, diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0bdddddab8de5..8a8cc5c5e448c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -7,10 +7,12 @@ /x-pack/plugins/discover_enhanced/ @elastic/kibana-app /x-pack/plugins/lens/ @elastic/kibana-app /x-pack/plugins/graph/ @elastic/kibana-app +/src/plugins/advanced_settings/ @elastic/kibana-app /src/plugins/charts/ @elastic/kibana-app /src/plugins/dashboard/ @elastic/kibana-app /src/plugins/discover/ @elastic/kibana-app /src/plugins/input_control_vis/ @elastic/kibana-app +/src/plugins/management/ @elastic/kibana-app /src/plugins/kibana_legacy/ @elastic/kibana-app /src/plugins/vis_default_editor/ @elastic/kibana-app /src/plugins/vis_type_markdown/ @elastic/kibana-app @@ -38,7 +40,6 @@ /examples/url_generators_explorer/ @elastic/kibana-app-arch /packages/elastic-datemath/ @elastic/kibana-app-arch /packages/kbn-interpreter/ @elastic/kibana-app-arch -/src/plugins/advanced_settings/ @elastic/kibana-app-arch /src/plugins/bfetch/ @elastic/kibana-app-arch /src/plugins/data/ @elastic/kibana-app-arch /src/plugins/embeddable/ @elastic/kibana-app-arch @@ -47,7 +48,6 @@ /src/plugins/kibana_react/ @elastic/kibana-app-arch /src/plugins/kibana_react/public/code_editor @elastic/kibana-canvas /src/plugins/kibana_utils/ @elastic/kibana-app-arch -/src/plugins/management/ @elastic/kibana-app-arch /src/plugins/navigation/ @elastic/kibana-app-arch /src/plugins/share/ @elastic/kibana-app-arch /src/plugins/ui_actions/ @elastic/kibana-app-arch @@ -65,14 +65,15 @@ # Client Side Monitoring (lives in APM directories but owned by Uptime) /x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm @elastic/uptime +/x-pack/plugins/apm/e2e/cypress/integration/csm_dashboard.feature @elastic/uptime /x-pack/plugins/apm/public/application/csmApp.tsx @elastic/uptime /x-pack/plugins/apm/public/components/app/RumDashboard @elastic/uptime /x-pack/plugins/apm/server/lib/rum_client @elastic/uptime /x-pack/plugins/apm/server/routes/rum_client.ts @elastic/uptime -/x-pack/plugins/apm/server/projections/rum_overview.ts @elastic/uptime +/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts @elastic/uptime # Beats -/x-pack/legacy/plugins/beats_management/ @elastic/beats +/x-pack/plugins/beats_management/ @elastic/beats # Canvas /x-pack/plugins/canvas/ @elastic/kibana-canvas @@ -86,16 +87,13 @@ /x-pack/plugins/global_search_bar/ @elastic/kibana-core-ui # Observability UIs -/x-pack/legacy/plugins/infra/ @elastic/logs-metrics-ui /x-pack/plugins/infra/ @elastic/logs-metrics-ui /x-pack/plugins/ingest_manager/ @elastic/ingest-management -/x-pack/legacy/plugins/ingest_manager/ @elastic/ingest-management /x-pack/plugins/observability/ @elastic/observability-ui /x-pack/plugins/monitoring/ @elastic/stack-monitoring-ui /x-pack/plugins/uptime @elastic/uptime # Machine Learning -/x-pack/legacy/plugins/ml/ @elastic/ml-ui /x-pack/plugins/ml/ @elastic/ml-ui /x-pack/test/functional/apps/machine_learning/ @elastic/ml-ui /x-pack/test/functional/services/machine_learning/ @elastic/ml-ui @@ -107,7 +105,6 @@ /x-pack/test/functional/services/transform.ts @elastic/ml-ui # Maps -/x-pack/legacy/plugins/maps/ @elastic/kibana-gis /x-pack/plugins/maps/ @elastic/kibana-gis /x-pack/test/api_integration/apis/maps/ @elastic/kibana-gis /x-pack/test/functional/apps/maps/ @elastic/kibana-gis @@ -234,13 +231,8 @@ x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @elastic/kib /src/plugins/dev_tools/ @elastic/es-ui /src/plugins/console/ @elastic/es-ui /src/plugins/es_ui_shared/ @elastic/es-ui -/x-pack/legacy/plugins/cross_cluster_replication/ @elastic/es-ui +/x-pack/plugins/cross_cluster_replication/ @elastic/es-ui /x-pack/plugins/index_lifecycle_management/ @elastic/es-ui -/x-pack/legacy/plugins/index_management/ @elastic/es-ui -/x-pack/legacy/plugins/license_management/ @elastic/es-ui -/x-pack/legacy/plugins/rollup/ @elastic/es-ui -/x-pack/legacy/plugins/snapshot_restore/ @elastic/es-ui -/x-pack/legacy/plugins/upgrade_assistant/ @elastic/es-ui /x-pack/plugins/console_extensions/ @elastic/es-ui /x-pack/plugins/es_ui_shared/ @elastic/es-ui /x-pack/plugins/grokdebugger/ @elastic/es-ui diff --git a/docs/apm/service-maps.asciidoc b/docs/apm/service-maps.asciidoc index db2f85c54c762..d629a95073a74 100644 --- a/docs/apm/service-maps.asciidoc +++ b/docs/apm/service-maps.asciidoc @@ -2,11 +2,6 @@ [[service-maps]] === Service maps -beta::[] - -WARNING: Service map support for Internet Explorer 11 is extremely limited. -Please use Chrome or Firefox if available. - A service map is a real-time visual representation of the instrumented services in your application's architecture. It shows you how these services are connected, along with high-level metrics like average transaction duration, requests per minute, and errors per minute. diff --git a/docs/developer/contributing/development-ci-metrics.asciidoc b/docs/developer/contributing/development-ci-metrics.asciidoc new file mode 100644 index 0000000000000..d4d54f1da7b8b --- /dev/null +++ b/docs/developer/contributing/development-ci-metrics.asciidoc @@ -0,0 +1,65 @@ +[[ci-metrics]] +== CI Metrics + +In addition to running our tests, CI collects metrics about the Kibana build. These metrics are sent to an external service to track changes over time, and to provide PR authors insights into the impact of their changes. + + +[[ci-metric-types]] +=== Metric types + + +[[ci-metric-types-bundle-size-metrics]] +==== Bundle size + +These metrics help contributors know how they are impacting the size of the bundles Kibana creates, and help make sure that Kibana loads as fast as possible. + +[[ci-metric-page-load-bundle-size]] `page load bundle size` :: +The size of the entry file produced for each bundle/plugin. This file is always loaded on every page load, so it should be as small as possible. To reduce this metric you can put any code that isn't necessary on every page load behind an https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#Dynamic_Imports[`async import()`]. ++ +Code that is shared statically with other plugins will contribute to the `page load bundle size` of that plugin. This includes exports from the `public/index.ts` file and any file referenced by the `extraPublicDirs` manifest property. + +[[ci-metric-async-chunks-size]] `async chunks size` :: +An "async chunk" is created for the files imported by each https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#Dynamic_Imports[`async import()`] statement. This metric tracks the sum size of these chunks, in bytes, broken down by plugin/bundle id. You can think of this as the amount of code users will have to download if they access all the components/applications within a bundle. + +[[ci-metric-misc-asset-size]] `miscellaneous assets size` :: +A "miscellaneous asset" is anything that isn't an async chunk or entry chunk, often images. This metric tracks the sum size of these assets, in bytes, broken down by plugin/bundle id. + +[[ci-metric-bundle-module-count]] `@kbn/optimizer bundle module count` :: +The number of separate modules included in each bundle/plugin. This is the best indicator we have for how long a specific bundle will take to be built by the `@kbn/optimizer`, so we report it to help people know when they've imported a module which might include a surprising number of sub-modules. + + +[[ci-metric-types-distributable-size]] +==== Distributable size + +The size of the Kibana distributable is an essential metric as it not only contributes to the time it takes to download, but it also impacts time it takes to extract the archive once downloaded. + +There are several metrics that we don't report on PRs because gzip-compression produces different file sizes even when provided the same input, so this metric would regularly show changes even though PR authors hadn't made any relevant changes. + +All metrics are collected from the `tar.gz` archive produced for the linux platform. + +[[ci-metric-distributable-file-count]] `distributable file count` :: +The number of files included in the default distributable. + +[[ci-metric-oss-distributable-file-count]] `oss distributable file count` :: +The number of files included in the OSS distributable. + +[[ci-metric-distributable-size]] `distributable size` :: +The size, in bytes, of the default distributable. _(not reported on PRs)_ + +[[ci-metric-oss-distributable-size]] `oss distributable size` :: +The size, in bytes, of the OSS distributable. _(not reported on PRs)_ + + +[[ci-metric-types-saved-object-field-counts]] +==== Saved Object field counts + +Elasticsearch limits the number of fields in an index to 1000 by default, and we want to avoid raising that limit. + +[[ci-metric-saved-object-field-count]] `Saved Objects .kibana field count` :: +The number of saved object fields broken down by saved object type. + + +[[ci-metric-adding-new-metrics]] +=== Adding new metrics + +You can report new metrics by using the `CiStatsReporter` class provided by the `@kbn/dev-utils` package. This class is automatically configured on CI and its methods noop when running outside of CI. For more details checkout the {kib-repo}blob/{branch}/packages/kbn-dev-utils/src/ci_stats_reporter[`CiStatsReporter` readme]. \ No newline at end of file diff --git a/docs/developer/contributing/index.asciidoc b/docs/developer/contributing/index.asciidoc index 99ab83bc2f073..ecb37ffe9c97b 100644 --- a/docs/developer/contributing/index.asciidoc +++ b/docs/developer/contributing/index.asciidoc @@ -9,6 +9,7 @@ Read <> to get your environment up and running, the * <> * <> * <> +* <> * <> * <> * <> @@ -78,6 +79,8 @@ include::development-tests.asciidoc[leveloffset=+1] include::interpreting-ci-failures.asciidoc[leveloffset=+1] +include::development-ci-metrics.asciidoc[leveloffset=+1] + include::development-documentation.asciidoc[leveloffset=+1] include::development-pull-request.asciidoc[leveloffset=+1] diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index b426621fed296..5a4a60c2e628e 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -504,6 +504,10 @@ in their infrastructure. |Contains HTTP endpoints and UiSettings that are slated for removal. +|{kib-repo}blob/{branch}/x-pack/plugins/drilldowns/url_drilldown/README.md[urlDrilldown] +|NOTE: This plugin contains implementation of URL drilldown. For drilldowns infrastructure code refer to ui_actions_enhanced plugin. + + |=== include::{kibana-root}/src/plugins/dashboard/README.asciidoc[leveloffset=+1] diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.md index 903462ac3039d..470a41f30afbf 100644 --- a/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.md +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.md @@ -29,4 +29,5 @@ export interface SavedObjectsFindOptions | [sortField](./kibana-plugin-core-public.savedobjectsfindoptions.sortfield.md) | string | | | [sortOrder](./kibana-plugin-core-public.savedobjectsfindoptions.sortorder.md) | string | | | [type](./kibana-plugin-core-public.savedobjectsfindoptions.type.md) | string | string[] | | +| [typeToNamespacesMap](./kibana-plugin-core-public.savedobjectsfindoptions.typetonamespacesmap.md) | Map<string, string[] | undefined> | This map defines each type to search for, and the namespace(s) to search for the type in; this is only intended to be used by a saved object client wrapper. If this is defined, it supersedes the type and namespaces fields when building the Elasticsearch query. Any types that are not included in this map will be excluded entirely. If a type is included but its value is undefined, the operation will search for that type in the Default namespace. | diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.typetonamespacesmap.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.typetonamespacesmap.md new file mode 100644 index 0000000000000..4af8c9ddeaff4 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.typetonamespacesmap.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [SavedObjectsFindOptions](./kibana-plugin-core-public.savedobjectsfindoptions.md) > [typeToNamespacesMap](./kibana-plugin-core-public.savedobjectsfindoptions.typetonamespacesmap.md) + +## SavedObjectsFindOptions.typeToNamespacesMap property + +This map defines each type to search for, and the namespace(s) to search for the type in; this is only intended to be used by a saved object client wrapper. If this is defined, it supersedes the `type` and `namespaces` fields when building the Elasticsearch query. Any types that are not included in this map will be excluded entirely. If a type is included but its value is undefined, the operation will search for that type in the Default namespace. + +Signature: + +```typescript +typeToNamespacesMap?: Map; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.md index 804c83f7c1b48..ce5c20e60ca11 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.md @@ -29,4 +29,5 @@ export interface SavedObjectsFindOptions | [sortField](./kibana-plugin-core-server.savedobjectsfindoptions.sortfield.md) | string | | | [sortOrder](./kibana-plugin-core-server.savedobjectsfindoptions.sortorder.md) | string | | | [type](./kibana-plugin-core-server.savedobjectsfindoptions.type.md) | string | string[] | | +| [typeToNamespacesMap](./kibana-plugin-core-server.savedobjectsfindoptions.typetonamespacesmap.md) | Map<string, string[] | undefined> | This map defines each type to search for, and the namespace(s) to search for the type in; this is only intended to be used by a saved object client wrapper. If this is defined, it supersedes the type and namespaces fields when building the Elasticsearch query. Any types that are not included in this map will be excluded entirely. If a type is included but its value is undefined, the operation will search for that type in the Default namespace. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.typetonamespacesmap.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.typetonamespacesmap.md new file mode 100644 index 0000000000000..8bec759f05580 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.typetonamespacesmap.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsFindOptions](./kibana-plugin-core-server.savedobjectsfindoptions.md) > [typeToNamespacesMap](./kibana-plugin-core-server.savedobjectsfindoptions.typetonamespacesmap.md) + +## SavedObjectsFindOptions.typeToNamespacesMap property + +This map defines each type to search for, and the namespace(s) to search for the type in; this is only intended to be used by a saved object client wrapper. If this is defined, it supersedes the `type` and `namespaces` fields when building the Elasticsearch query. Any types that are not included in this map will be excluded entirely. If a type is included but its value is undefined, the operation will search for that type in the Default namespace. + +Signature: + +```typescript +typeToNamespacesMap?: Map; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.find.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.find.md index 1b562263145da..d3e93e7af2aa0 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.find.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.find.md @@ -7,14 +7,14 @@ Signature: ```typescript -find({ search, defaultSearchOperator, searchFields, rootSearchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespaces, type, filter, preference, }: SavedObjectsFindOptions): Promise>; +find(options: SavedObjectsFindOptions): Promise>; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| { search, defaultSearchOperator, searchFields, rootSearchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespaces, type, filter, preference, } | SavedObjectsFindOptions | | +| options | SavedObjectsFindOptions | | Returns: diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.md index 14d3741425987..1d11d5262a9c4 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.md @@ -24,7 +24,7 @@ export declare class SavedObjectsRepository | [delete(type, id, options)](./kibana-plugin-core-server.savedobjectsrepository.delete.md) | | Deletes an object | | [deleteByNamespace(namespace, options)](./kibana-plugin-core-server.savedobjectsrepository.deletebynamespace.md) | | Deletes all objects from the provided namespace. | | [deleteFromNamespaces(type, id, namespaces, options)](./kibana-plugin-core-server.savedobjectsrepository.deletefromnamespaces.md) | | Removes one or more namespaces from a given multi-namespace saved object. If no namespaces remain, the saved object is deleted entirely. This method and \[addToNamespaces\][SavedObjectsRepository.addToNamespaces()](./kibana-plugin-core-server.savedobjectsrepository.addtonamespaces.md) are the only ways to change which Spaces a multi-namespace saved object is shared to. | -| [find({ search, defaultSearchOperator, searchFields, rootSearchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespaces, type, filter, preference, })](./kibana-plugin-core-server.savedobjectsrepository.find.md) | | | +| [find(options)](./kibana-plugin-core-server.savedobjectsrepository.find.md) | | | | [get(type, id, options)](./kibana-plugin-core-server.savedobjectsrepository.get.md) | | Gets a single object | | [incrementCounter(type, id, counterFieldName, options)](./kibana-plugin-core-server.savedobjectsrepository.incrementcounter.md) | | Increases a counter field by one. Creates the document if one doesn't exist for the given id. | | [update(type, id, attributes, options)](./kibana-plugin-core-server.savedobjectsrepository.update.md) | | Updates an object | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsutils.createemptyfindresponse.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsutils.createemptyfindresponse.md new file mode 100644 index 0000000000000..40e865cb02ce8 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsutils.createemptyfindresponse.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsUtils](./kibana-plugin-core-server.savedobjectsutils.md) > [createEmptyFindResponse](./kibana-plugin-core-server.savedobjectsutils.createemptyfindresponse.md) + +## SavedObjectsUtils.createEmptyFindResponse property + +Creates an empty response for a find operation. This is only intended to be used by saved objects client wrappers. + +Signature: + +```typescript +static createEmptyFindResponse: ({ page, perPage, }: SavedObjectsFindOptions) => SavedObjectsFindResponse; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsutils.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsutils.md index e365dfbcb5142..83831f65bd41a 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsutils.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsutils.md @@ -15,6 +15,7 @@ export declare class SavedObjectsUtils | Property | Modifiers | Type | Description | | --- | --- | --- | --- | +| [createEmptyFindResponse](./kibana-plugin-core-server.savedobjectsutils.createemptyfindresponse.md) | static | <T>({ page, perPage, }: SavedObjectsFindOptions) => SavedObjectsFindResponse<T> | Creates an empty response for a find operation. This is only intended to be used by saved objects client wrappers. | | [namespaceIdToString](./kibana-plugin-core-server.savedobjectsutils.namespaceidtostring.md) | static | (namespace?: string | undefined) => string | Converts a given saved object namespace ID to its string representation. All namespace IDs have an identical string representation, with the exception of the undefined namespace ID (which has a namespace string of 'default'). | | [namespaceStringToId](./kibana-plugin-core-server.savedobjectsutils.namespacestringtoid.md) | static | (namespace: string) => string | undefined | Converts a given saved object namespace string to its ID representation. All namespace strings have an identical ID representation, with the exception of the 'default' namespace string (which has a namespace ID of undefined). | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.duplicateindexpatternerror._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.duplicateindexpatternerror._constructor_.md new file mode 100644 index 0000000000000..676f1a2c785f8 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.duplicateindexpatternerror._constructor_.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [DuplicateIndexPatternError](./kibana-plugin-plugins-data-public.duplicateindexpatternerror.md) > [(constructor)](./kibana-plugin-plugins-data-public.duplicateindexpatternerror._constructor_.md) + +## DuplicateIndexPatternError.(constructor) + +Constructs a new instance of the `DuplicateIndexPatternError` class + +Signature: + +```typescript +constructor(message: string); +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| message | string | | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.duplicateindexpatternerror.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.duplicateindexpatternerror.md new file mode 100644 index 0000000000000..7ed8f97976464 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.duplicateindexpatternerror.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [DuplicateIndexPatternError](./kibana-plugin-plugins-data-public.duplicateindexpatternerror.md) + +## DuplicateIndexPatternError class + +Signature: + +```typescript +export declare class DuplicateIndexPatternError extends Error +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(message)](./kibana-plugin-plugins-data-public.duplicateindexpatternerror._constructor_.md) | | Constructs a new instance of the DuplicateIndexPatternError class | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.fieldformatmap.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.fieldformatmap.md index 2c131c6da9937..60ac95bc21af2 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.fieldformatmap.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.fieldformatmap.md @@ -7,8 +7,5 @@ Signature: ```typescript -fieldFormatMap?: Record; +fieldFormatMap?: Record | undefined>; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.getformatterforfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.getformatterforfield.md new file mode 100644 index 0000000000000..7466e4b9cf658 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.getformatterforfield.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IIndexPattern](./kibana-plugin-plugins-data-public.iindexpattern.md) > [getFormatterForField](./kibana-plugin-plugins-data-public.iindexpattern.getformatterforfield.md) + +## IIndexPattern.getFormatterForField property + +Signature: + +```typescript +getFormatterForField?: (field: IndexPatternField | IndexPatternField['spec'] | IFieldType) => FieldFormat; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.md index 1cb89822eb605..ba77e659f0834 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.md @@ -14,8 +14,9 @@ export interface IIndexPattern | Property | Type | Description | | --- | --- | --- | -| [fieldFormatMap](./kibana-plugin-plugins-data-public.iindexpattern.fieldformatmap.md) | Record<string, {
id: string;
params: unknown;
}> | | +| [fieldFormatMap](./kibana-plugin-plugins-data-public.iindexpattern.fieldformatmap.md) | Record<string, SerializedFieldFormat<unknown> | undefined> | | | [fields](./kibana-plugin-plugins-data-public.iindexpattern.fields.md) | IFieldType[] | | +| [getFormatterForField](./kibana-plugin-plugins-data-public.iindexpattern.getformatterforfield.md) | (field: IndexPatternField | IndexPatternField['spec'] | IFieldType) => FieldFormat | | | [id](./kibana-plugin-plugins-data-public.iindexpattern.id.md) | string | | | [timeFieldName](./kibana-plugin-plugins-data-public.iindexpattern.timefieldname.md) | string | | | [title](./kibana-plugin-plugins-data-public.iindexpattern.title.md) | string | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.tospec.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.tospec.md index fd20f2944c5be..0fe62f575a927 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.tospec.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.tospec.md @@ -9,7 +9,7 @@ ```typescript toSpec(options?: { getFormatterForField?: IndexPattern['getFormatterForField']; - }): FieldSpec[]; + }): IndexPatternFieldMap; ``` ## Parameters @@ -20,5 +20,5 @@ toSpec(options?: { Returns: -`FieldSpec[]` +`IndexPatternFieldMap` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern._constructor_.md index a5bb15c963978..4baf98038f89a 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern._constructor_.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern._constructor_.md @@ -9,13 +9,12 @@ Constructs a new instance of the `IndexPattern` class Signature: ```typescript -constructor(id: string | undefined, { savedObjectsClient, apiClient, patternCache, fieldFormats, indexPatternsService, onNotification, onError, shortDotsEnable, metaFields, }: IndexPatternDeps); +constructor({ spec, savedObjectsClient, fieldFormats, shortDotsEnable, metaFields, }: IndexPatternDeps); ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| id | string | undefined | | -| { savedObjectsClient, apiClient, patternCache, fieldFormats, indexPatternsService, onNotification, onError, shortDotsEnable, metaFields, } | IndexPatternDeps | | +| { spec, savedObjectsClient, fieldFormats, shortDotsEnable, metaFields, } | IndexPatternDeps | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern._fetchfields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern._fetchfields.md deleted file mode 100644 index 8fff8baa71139..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern._fetchfields.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [\_fetchFields](./kibana-plugin-plugins-data-public.indexpattern._fetchfields.md) - -## IndexPattern.\_fetchFields() method - -Signature: - -```typescript -_fetchFields(): Promise; -``` -Returns: - -`Promise` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.addscriptedfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.addscriptedfield.md index 4bbbd83c65e10..cc3468531fffa 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.addscriptedfield.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.addscriptedfield.md @@ -4,10 +4,12 @@ ## IndexPattern.addScriptedField() method +Add scripted field to field list + Signature: ```typescript -addScriptedField(name: string, script: string, fieldType: string | undefined, lang: string): Promise; +addScriptedField(name: string, script: string, fieldType?: string, lang?: string): Promise; ``` ## Parameters @@ -16,7 +18,7 @@ addScriptedField(name: string, script: string, fieldType: string | undefined, la | --- | --- | --- | | name | string | | | script | string | | -| fieldType | string | undefined | | +| fieldType | string | | | lang | string | | Returns: diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.create.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.create.md deleted file mode 100644 index 5c122b835f59d..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.create.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [create](./kibana-plugin-plugins-data-public.indexpattern.create.md) - -## IndexPattern.create() method - -Signature: - -```typescript -create(allowOverride?: boolean): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| allowOverride | boolean | | - -Returns: - -`Promise` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fieldformatmap.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fieldformatmap.md index b89b244d9826c..904d52fcd5751 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fieldformatmap.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fieldformatmap.md @@ -7,5 +7,5 @@ Signature: ```typescript -fieldFormatMap: any; +fieldFormatMap: Record; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fields.md index d4dca48c7cd7b..76bc41238526e 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fields.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fields.md @@ -8,6 +8,6 @@ ```typescript fields: IIndexPatternFieldList & { - toSpec: () => FieldSpec[]; + toSpec: () => IndexPatternFieldMap; }; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fieldsfetcher.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fieldsfetcher.md deleted file mode 100644 index 4d44b386a1db1..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fieldsfetcher.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [fieldsFetcher](./kibana-plugin-plugins-data-public.indexpattern.fieldsfetcher.md) - -## IndexPattern.fieldsFetcher property - -Signature: - -```typescript -fieldsFetcher: any; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.flattenhit.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.flattenhit.md index db28d95197bb3..049c3e5e990f7 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.flattenhit.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.flattenhit.md @@ -7,5 +7,5 @@ Signature: ```typescript -flattenHit: any; +flattenHit: (hit: Record, deep?: boolean) => Record; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.formatfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.formatfield.md index 5a475d6161ac3..aadaddca6cc85 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.formatfield.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.formatfield.md @@ -7,5 +7,5 @@ Signature: ```typescript -formatField: any; +formatField: FormatFieldFn; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.formathit.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.formathit.md index ac515d374a93f..2be76bf1c1e05 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.formathit.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.formathit.md @@ -7,5 +7,8 @@ Signature: ```typescript -formatHit: any; +formatHit: { + (hit: Record, type?: string): any; + formatField: FormatFieldFn; + }; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.prepbody.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getassavedobjectbody.md similarity index 76% rename from docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.prepbody.md rename to docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getassavedobjectbody.md index 1d77b2a55860e..2c5f30e4889ea 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.prepbody.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getassavedobjectbody.md @@ -1,13 +1,15 @@ -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [prepBody](./kibana-plugin-plugins-data-public.indexpattern.prepbody.md) +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [getAsSavedObjectBody](./kibana-plugin-plugins-data-public.indexpattern.getassavedobjectbody.md) -## IndexPattern.prepBody() method +## IndexPattern.getAsSavedObjectBody() method + +Returns index pattern as saved object body for saving Signature: ```typescript -prepBody(): { +getAsSavedObjectBody(): { title: string; timeFieldName: string | undefined; intervalName: string | undefined; diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getformatterforfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getformatterforfield.md index 7984f7aff1d2d..ba31d60b56892 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getformatterforfield.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getformatterforfield.md @@ -4,17 +4,19 @@ ## IndexPattern.getFormatterForField() method +Provide a field, get its formatter + Signature: ```typescript -getFormatterForField(field: IndexPatternField | IndexPatternField['spec']): FieldFormat; +getFormatterForField(field: IndexPatternField | IndexPatternField['spec'] | IFieldType): FieldFormat; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| field | IndexPatternField | IndexPatternField['spec'] | | +| field | IndexPatternField | IndexPatternField['spec'] | IFieldType | | Returns: diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getoriginalsavedobjectbody.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getoriginalsavedobjectbody.md new file mode 100644 index 0000000000000..349da63c13ca7 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getoriginalsavedobjectbody.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [getOriginalSavedObjectBody](./kibana-plugin-plugins-data-public.indexpattern.getoriginalsavedobjectbody.md) + +## IndexPattern.getOriginalSavedObjectBody property + +Get last saved saved object fields + +Signature: + +```typescript +getOriginalSavedObjectBody: () => { + title?: string | undefined; + timeFieldName?: string | undefined; + intervalName?: string | undefined; + fields?: string | undefined; + sourceFilters?: string | undefined; + fieldFormatMap?: string | undefined; + typeMeta?: string | undefined; + type?: string | undefined; + }; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getsourcefiltering.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getsourcefiltering.md index 121d32c7c40c8..4ce0144b73882 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getsourcefiltering.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getsourcefiltering.md @@ -4,6 +4,8 @@ ## IndexPattern.getSourceFiltering() method +Get the source filtering configuration for that index. + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.init.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.init.md deleted file mode 100644 index 595992dc82b74..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.init.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [init](./kibana-plugin-plugins-data-public.indexpattern.init.md) - -## IndexPattern.init() method - -Signature: - -```typescript -init(): Promise; -``` -Returns: - -`Promise` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.initfromspec.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.initfromspec.md deleted file mode 100644 index 764dd11638221..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.initfromspec.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [initFromSpec](./kibana-plugin-plugins-data-public.indexpattern.initfromspec.md) - -## IndexPattern.initFromSpec() method - -Signature: - -```typescript -initFromSpec(spec: IndexPatternSpec): this; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| spec | IndexPatternSpec | | - -Returns: - -`this` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.iswildcard.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.iswildcard.md deleted file mode 100644 index e5ea55ef1dd48..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.iswildcard.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [isWildcard](./kibana-plugin-plugins-data-public.indexpattern.iswildcard.md) - -## IndexPattern.isWildcard() method - -Signature: - -```typescript -isWildcard(): boolean; -``` -Returns: - -`boolean` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md index 87ce1e258712a..2ff575bc4fc22 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md @@ -14,22 +14,22 @@ export declare class IndexPattern implements IIndexPattern | Constructor | Modifiers | Description | | --- | --- | --- | -| [(constructor)(id, { savedObjectsClient, apiClient, patternCache, fieldFormats, indexPatternsService, onNotification, onError, shortDotsEnable, metaFields, })](./kibana-plugin-plugins-data-public.indexpattern._constructor_.md) | | Constructs a new instance of the IndexPattern class | +| [(constructor)({ spec, savedObjectsClient, fieldFormats, shortDotsEnable, metaFields, })](./kibana-plugin-plugins-data-public.indexpattern._constructor_.md) | | Constructs a new instance of the IndexPattern class | ## Properties | Property | Modifiers | Type | Description | | --- | --- | --- | --- | -| [fieldFormatMap](./kibana-plugin-plugins-data-public.indexpattern.fieldformatmap.md) | | any | | -| [fields](./kibana-plugin-plugins-data-public.indexpattern.fields.md) | | IIndexPatternFieldList & {
toSpec: () => FieldSpec[];
} | | -| [fieldsFetcher](./kibana-plugin-plugins-data-public.indexpattern.fieldsfetcher.md) | | any | | -| [flattenHit](./kibana-plugin-plugins-data-public.indexpattern.flattenhit.md) | | any | | -| [formatField](./kibana-plugin-plugins-data-public.indexpattern.formatfield.md) | | any | | -| [formatHit](./kibana-plugin-plugins-data-public.indexpattern.formathit.md) | | any | | +| [fieldFormatMap](./kibana-plugin-plugins-data-public.indexpattern.fieldformatmap.md) | | Record<string, any> | | +| [fields](./kibana-plugin-plugins-data-public.indexpattern.fields.md) | | IIndexPatternFieldList & {
toSpec: () => IndexPatternFieldMap;
} | | +| [flattenHit](./kibana-plugin-plugins-data-public.indexpattern.flattenhit.md) | | (hit: Record<string, any>, deep?: boolean) => Record<string, any> | | +| [formatField](./kibana-plugin-plugins-data-public.indexpattern.formatfield.md) | | FormatFieldFn | | +| [formatHit](./kibana-plugin-plugins-data-public.indexpattern.formathit.md) | | {
(hit: Record<string, any>, type?: string): any;
formatField: FormatFieldFn;
} | | +| [getOriginalSavedObjectBody](./kibana-plugin-plugins-data-public.indexpattern.getoriginalsavedobjectbody.md) | | () => {
title?: string | undefined;
timeFieldName?: string | undefined;
intervalName?: string | undefined;
fields?: string | undefined;
sourceFilters?: string | undefined;
fieldFormatMap?: string | undefined;
typeMeta?: string | undefined;
type?: string | undefined;
} | Get last saved saved object fields | | [id](./kibana-plugin-plugins-data-public.indexpattern.id.md) | | string | | | [intervalName](./kibana-plugin-plugins-data-public.indexpattern.intervalname.md) | | string | undefined | | | [metaFields](./kibana-plugin-plugins-data-public.indexpattern.metafields.md) | | string[] | | -| [originalBody](./kibana-plugin-plugins-data-public.indexpattern.originalbody.md) | | {
[key: string]: any;
} | | +| [resetOriginalSavedObjectBody](./kibana-plugin-plugins-data-public.indexpattern.resetoriginalsavedobjectbody.md) | | () => void | Reset last saved saved object fields. used after saving | | [sourceFilters](./kibana-plugin-plugins-data-public.indexpattern.sourcefilters.md) | | SourceFilter[] | | | [timeFieldName](./kibana-plugin-plugins-data-public.indexpattern.timefieldname.md) | | string | undefined | | | [title](./kibana-plugin-plugins-data-public.indexpattern.title.md) | | string | | @@ -41,26 +41,20 @@ export declare class IndexPattern implements IIndexPattern | Method | Modifiers | Description | | --- | --- | --- | -| [\_fetchFields()](./kibana-plugin-plugins-data-public.indexpattern._fetchfields.md) | | | -| [addScriptedField(name, script, fieldType, lang)](./kibana-plugin-plugins-data-public.indexpattern.addscriptedfield.md) | | | -| [create(allowOverride)](./kibana-plugin-plugins-data-public.indexpattern.create.md) | | | +| [addScriptedField(name, script, fieldType, lang)](./kibana-plugin-plugins-data-public.indexpattern.addscriptedfield.md) | | Add scripted field to field list | | [getAggregationRestrictions()](./kibana-plugin-plugins-data-public.indexpattern.getaggregationrestrictions.md) | | | +| [getAsSavedObjectBody()](./kibana-plugin-plugins-data-public.indexpattern.getassavedobjectbody.md) | | Returns index pattern as saved object body for saving | | [getComputedFields()](./kibana-plugin-plugins-data-public.indexpattern.getcomputedfields.md) | | | | [getFieldByName(name)](./kibana-plugin-plugins-data-public.indexpattern.getfieldbyname.md) | | | -| [getFormatterForField(field)](./kibana-plugin-plugins-data-public.indexpattern.getformatterforfield.md) | | | +| [getFormatterForField(field)](./kibana-plugin-plugins-data-public.indexpattern.getformatterforfield.md) | | Provide a field, get its formatter | | [getNonScriptedFields()](./kibana-plugin-plugins-data-public.indexpattern.getnonscriptedfields.md) | | | | [getScriptedFields()](./kibana-plugin-plugins-data-public.indexpattern.getscriptedfields.md) | | | -| [getSourceFiltering()](./kibana-plugin-plugins-data-public.indexpattern.getsourcefiltering.md) | | | +| [getSourceFiltering()](./kibana-plugin-plugins-data-public.indexpattern.getsourcefiltering.md) | | Get the source filtering configuration for that index. | | [getTimeField()](./kibana-plugin-plugins-data-public.indexpattern.gettimefield.md) | | | -| [init()](./kibana-plugin-plugins-data-public.indexpattern.init.md) | | | -| [initFromSpec(spec)](./kibana-plugin-plugins-data-public.indexpattern.initfromspec.md) | | | | [isTimeBased()](./kibana-plugin-plugins-data-public.indexpattern.istimebased.md) | | | | [isTimeBasedWildcard()](./kibana-plugin-plugins-data-public.indexpattern.istimebasedwildcard.md) | | | | [isTimeNanosBased()](./kibana-plugin-plugins-data-public.indexpattern.istimenanosbased.md) | | | -| [isWildcard()](./kibana-plugin-plugins-data-public.indexpattern.iswildcard.md) | | | | [popularizeField(fieldName, unit)](./kibana-plugin-plugins-data-public.indexpattern.popularizefield.md) | | | -| [prepBody()](./kibana-plugin-plugins-data-public.indexpattern.prepbody.md) | | | -| [refreshFields()](./kibana-plugin-plugins-data-public.indexpattern.refreshfields.md) | | | -| [removeScriptedField(fieldName)](./kibana-plugin-plugins-data-public.indexpattern.removescriptedfield.md) | | | +| [removeScriptedField(fieldName)](./kibana-plugin-plugins-data-public.indexpattern.removescriptedfield.md) | | Remove scripted field from field list | | [toSpec()](./kibana-plugin-plugins-data-public.indexpattern.tospec.md) | | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.originalbody.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.originalbody.md deleted file mode 100644 index 4bc3c76afbae9..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.originalbody.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [originalBody](./kibana-plugin-plugins-data-public.indexpattern.originalbody.md) - -## IndexPattern.originalBody property - -Signature: - -```typescript -originalBody: { - [key: string]: any; - }; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.refreshfields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.refreshfields.md deleted file mode 100644 index 271d0c45a4244..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.refreshfields.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [refreshFields](./kibana-plugin-plugins-data-public.indexpattern.refreshfields.md) - -## IndexPattern.refreshFields() method - -Signature: - -```typescript -refreshFields(): Promise; -``` -Returns: - -`Promise` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removescriptedfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removescriptedfield.md index e902d9c42b082..aaaebdaccca5d 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removescriptedfield.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.removescriptedfield.md @@ -4,6 +4,8 @@ ## IndexPattern.removeScriptedField() method +Remove scripted field from field list + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.resetoriginalsavedobjectbody.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.resetoriginalsavedobjectbody.md new file mode 100644 index 0000000000000..6bbc13d8fd410 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.resetoriginalsavedobjectbody.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [resetOriginalSavedObjectBody](./kibana-plugin-plugins-data-public.indexpattern.resetoriginalsavedobjectbody.md) + +## IndexPattern.resetOriginalSavedObjectBody property + +Reset last saved saved object fields. used after saving + +Signature: + +```typescript +resetOriginalSavedObjectBody: () => void; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.md index eff2349f053ff..77a8ebb0b2d3f 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.md @@ -4,12 +4,6 @@ ## IndexPatternAttributes interface -> Warning: This API is now obsolete. -> -> - -Use data plugin interface instead - Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.conflictdescriptions.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.conflictdescriptions.md index 6d62053726197..9b226266f0b5a 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.conflictdescriptions.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.conflictdescriptions.md @@ -4,6 +4,8 @@ ## IndexPatternField.conflictDescriptions property +Description of field type conflicts across different indices in the same index pattern + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.count.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.count.md index 84c0a75fd206d..1b8e13a38c6d9 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.count.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.count.md @@ -4,6 +4,8 @@ ## IndexPatternField.count property +Count is used for field popularity + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.lang.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.lang.md index 0a8446d40e5ec..b81218eb08886 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.lang.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.lang.md @@ -4,6 +4,8 @@ ## IndexPatternField.lang property +Script field language + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md index 215188ffa2607..4f49a9a8fc3ab 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md @@ -21,15 +21,15 @@ export declare class IndexPatternField implements IFieldType | Property | Modifiers | Type | Description | | --- | --- | --- | --- | | [aggregatable](./kibana-plugin-plugins-data-public.indexpatternfield.aggregatable.md) | | boolean | | -| [conflictDescriptions](./kibana-plugin-plugins-data-public.indexpatternfield.conflictdescriptions.md) | | Record<string, string[]> | undefined | | -| [count](./kibana-plugin-plugins-data-public.indexpatternfield.count.md) | | number | | +| [conflictDescriptions](./kibana-plugin-plugins-data-public.indexpatternfield.conflictdescriptions.md) | | Record<string, string[]> | undefined | Description of field type conflicts across different indices in the same index pattern | +| [count](./kibana-plugin-plugins-data-public.indexpatternfield.count.md) | | number | Count is used for field popularity | | [displayName](./kibana-plugin-plugins-data-public.indexpatternfield.displayname.md) | | string | | | [esTypes](./kibana-plugin-plugins-data-public.indexpatternfield.estypes.md) | | string[] | undefined | | | [filterable](./kibana-plugin-plugins-data-public.indexpatternfield.filterable.md) | | boolean | | -| [lang](./kibana-plugin-plugins-data-public.indexpatternfield.lang.md) | | string | undefined | | +| [lang](./kibana-plugin-plugins-data-public.indexpatternfield.lang.md) | | string | undefined | Script field language | | [name](./kibana-plugin-plugins-data-public.indexpatternfield.name.md) | | string | | | [readFromDocValues](./kibana-plugin-plugins-data-public.indexpatternfield.readfromdocvalues.md) | | boolean | | -| [script](./kibana-plugin-plugins-data-public.indexpatternfield.script.md) | | string | undefined | | +| [script](./kibana-plugin-plugins-data-public.indexpatternfield.script.md) | | string | undefined | Script field code | | [scripted](./kibana-plugin-plugins-data-public.indexpatternfield.scripted.md) | | boolean | | | [searchable](./kibana-plugin-plugins-data-public.indexpatternfield.searchable.md) | | boolean | | | [sortable](./kibana-plugin-plugins-data-public.indexpatternfield.sortable.md) | | boolean | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.script.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.script.md index 27f9c797c92f2..7501e191d9363 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.script.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.script.md @@ -4,6 +4,8 @@ ## IndexPatternField.script property +Script field code + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tospec.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tospec.md index 1d80c90991f55..711d6ad660450 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tospec.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tospec.md @@ -9,24 +9,7 @@ ```typescript toSpec({ getFormatterForField, }?: { getFormatterForField?: IndexPattern['getFormatterForField']; - }): { - count: number; - script: string | undefined; - lang: string | undefined; - conflictDescriptions: Record | undefined; - name: string; - type: string; - esTypes: string[] | undefined; - scripted: boolean; - searchable: boolean; - aggregatable: boolean; - readFromDocValues: boolean; - subType: import("../types").IFieldSubType | undefined; - format: { - id: any; - params: any; - } | undefined; - }; + }): FieldSpec; ``` ## Parameters @@ -37,22 +20,5 @@ toSpec({ getFormatterForField, }?: { Returns: -`{ - count: number; - script: string | undefined; - lang: string | undefined; - conflictDescriptions: Record | undefined; - name: string; - type: string; - esTypes: string[] | undefined; - scripted: boolean; - searchable: boolean; - aggregatable: boolean; - readFromDocValues: boolean; - subType: import("../types").IFieldSubType | undefined; - format: { - id: any; - params: any; - } | undefined; - }` +`FieldSpec` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.fields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.fields.md new file mode 100644 index 0000000000000..386e080dbe6c2 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.fields.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternSpec](./kibana-plugin-plugins-data-public.indexpatternspec.md) > [fields](./kibana-plugin-plugins-data-public.indexpatternspec.fields.md) + +## IndexPatternSpec.fields property + +Signature: + +```typescript +fields?: IndexPatternFieldMap; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.id.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.id.md new file mode 100644 index 0000000000000..55eadbf36c660 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.id.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternSpec](./kibana-plugin-plugins-data-public.indexpatternspec.md) > [id](./kibana-plugin-plugins-data-public.indexpatternspec.id.md) + +## IndexPatternSpec.id property + +Signature: + +```typescript +id?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.intervalname.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.intervalname.md new file mode 100644 index 0000000000000..98748661256da --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.intervalname.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternSpec](./kibana-plugin-plugins-data-public.indexpatternspec.md) > [intervalName](./kibana-plugin-plugins-data-public.indexpatternspec.intervalname.md) + +## IndexPatternSpec.intervalName property + +Signature: + +```typescript +intervalName?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.md new file mode 100644 index 0000000000000..74c4df126e1bf --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternSpec](./kibana-plugin-plugins-data-public.indexpatternspec.md) + +## IndexPatternSpec interface + +Signature: + +```typescript +export interface IndexPatternSpec +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [fields](./kibana-plugin-plugins-data-public.indexpatternspec.fields.md) | IndexPatternFieldMap | | +| [id](./kibana-plugin-plugins-data-public.indexpatternspec.id.md) | string | | +| [intervalName](./kibana-plugin-plugins-data-public.indexpatternspec.intervalname.md) | string | | +| [sourceFilters](./kibana-plugin-plugins-data-public.indexpatternspec.sourcefilters.md) | SourceFilter[] | | +| [timeFieldName](./kibana-plugin-plugins-data-public.indexpatternspec.timefieldname.md) | string | | +| [title](./kibana-plugin-plugins-data-public.indexpatternspec.title.md) | string | | +| [type](./kibana-plugin-plugins-data-public.indexpatternspec.type.md) | string | | +| [typeMeta](./kibana-plugin-plugins-data-public.indexpatternspec.typemeta.md) | TypeMeta | | +| [version](./kibana-plugin-plugins-data-public.indexpatternspec.version.md) | string | | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.sourcefilters.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.sourcefilters.md new file mode 100644 index 0000000000000..cda5285730135 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.sourcefilters.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternSpec](./kibana-plugin-plugins-data-public.indexpatternspec.md) > [sourceFilters](./kibana-plugin-plugins-data-public.indexpatternspec.sourcefilters.md) + +## IndexPatternSpec.sourceFilters property + +Signature: + +```typescript +sourceFilters?: SourceFilter[]; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.timefieldname.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.timefieldname.md new file mode 100644 index 0000000000000..a527e3ac0658b --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.timefieldname.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternSpec](./kibana-plugin-plugins-data-public.indexpatternspec.md) > [timeFieldName](./kibana-plugin-plugins-data-public.indexpatternspec.timefieldname.md) + +## IndexPatternSpec.timeFieldName property + +Signature: + +```typescript +timeFieldName?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.title.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.title.md new file mode 100644 index 0000000000000..4cc6d3c2524a7 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.title.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternSpec](./kibana-plugin-plugins-data-public.indexpatternspec.md) > [title](./kibana-plugin-plugins-data-public.indexpatternspec.title.md) + +## IndexPatternSpec.title property + +Signature: + +```typescript +title?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.type.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.type.md new file mode 100644 index 0000000000000..d1c49be1b706f --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.type.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternSpec](./kibana-plugin-plugins-data-public.indexpatternspec.md) > [type](./kibana-plugin-plugins-data-public.indexpatternspec.type.md) + +## IndexPatternSpec.type property + +Signature: + +```typescript +type?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.typemeta.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.typemeta.md new file mode 100644 index 0000000000000..9303047e905d3 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.typemeta.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternSpec](./kibana-plugin-plugins-data-public.indexpatternspec.md) > [typeMeta](./kibana-plugin-plugins-data-public.indexpatternspec.typemeta.md) + +## IndexPatternSpec.typeMeta property + +Signature: + +```typescript +typeMeta?: TypeMeta; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.version.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.version.md new file mode 100644 index 0000000000000..43f7cf0226fb0 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.version.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternSpec](./kibana-plugin-plugins-data-public.indexpatternspec.md) > [version](./kibana-plugin-plugins-data-public.indexpatternspec.version.md) + +## IndexPatternSpec.version property + +Signature: + +```typescript +version?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice._constructor_.md new file mode 100644 index 0000000000000..ab397efb1fe0e --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice._constructor_.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [(constructor)](./kibana-plugin-plugins-data-public.indexpatternsservice._constructor_.md) + +## IndexPatternsService.(constructor) + +Constructs a new instance of the `IndexPatternsService` class + +Signature: + +```typescript +constructor({ uiSettings, savedObjectsClient, apiClient, fieldFormats, onNotification, onError, onRedirectNoIndexPattern, }: IndexPatternsServiceDeps); +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| { uiSettings, savedObjectsClient, apiClient, fieldFormats, onNotification, onError, onRedirectNoIndexPattern, } | IndexPatternsServiceDeps | | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.clearcache.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.clearcache.md new file mode 100644 index 0000000000000..b371218325086 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.clearcache.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [clearCache](./kibana-plugin-plugins-data-public.indexpatternsservice.clearcache.md) + +## IndexPatternsService.clearCache property + +Clear index pattern list cache + +Signature: + +```typescript +clearCache: (id?: string | undefined) => void; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.create.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.create.md new file mode 100644 index 0000000000000..d7152ba617cc6 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.create.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [create](./kibana-plugin-plugins-data-public.indexpatternsservice.create.md) + +## IndexPatternsService.create() method + +Create a new index pattern instance + +Signature: + +```typescript +create(spec: IndexPatternSpec, skipFetchFields?: boolean): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| spec | IndexPatternSpec | | +| skipFetchFields | boolean | | + +Returns: + +`Promise` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.createandsave.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.createandsave.md new file mode 100644 index 0000000000000..eebfbb506fb77 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.createandsave.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [createAndSave](./kibana-plugin-plugins-data-public.indexpatternsservice.createandsave.md) + +## IndexPatternsService.createAndSave() method + +Create a new index pattern and save it right away + +Signature: + +```typescript +createAndSave(spec: IndexPatternSpec, override?: boolean, skipFetchFields?: boolean): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| spec | IndexPatternSpec | | +| override | boolean | | +| skipFetchFields | boolean | | + +Returns: + +`Promise` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.createsavedobject.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.createsavedobject.md new file mode 100644 index 0000000000000..8efb33c423b01 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.createsavedobject.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [createSavedObject](./kibana-plugin-plugins-data-public.indexpatternsservice.createsavedobject.md) + +## IndexPatternsService.createSavedObject() method + +Save a new index pattern + +Signature: + +```typescript +createSavedObject(indexPattern: IndexPattern, override?: boolean): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| indexPattern | IndexPattern | | +| override | boolean | | + +Returns: + +`Promise` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.delete.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.delete.md new file mode 100644 index 0000000000000..aba31ab2c0d29 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.delete.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [delete](./kibana-plugin-plugins-data-public.indexpatternsservice.delete.md) + +## IndexPatternsService.delete() method + +Deletes an index pattern from .kibana index + +Signature: + +```typescript +delete(indexPatternId: string): Promise<{}>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| indexPatternId | string | | + +Returns: + +`Promise<{}>` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.ensuredefaultindexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.ensuredefaultindexpattern.md new file mode 100644 index 0000000000000..3b6a8c7e4a04f --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.ensuredefaultindexpattern.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [ensureDefaultIndexPattern](./kibana-plugin-plugins-data-public.indexpatternsservice.ensuredefaultindexpattern.md) + +## IndexPatternsService.ensureDefaultIndexPattern property + +Signature: + +```typescript +ensureDefaultIndexPattern: EnsureDefaultIndexPattern; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.fieldarraytomap.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.fieldarraytomap.md new file mode 100644 index 0000000000000..ed365fe03f980 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.fieldarraytomap.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [fieldArrayToMap](./kibana-plugin-plugins-data-public.indexpatternsservice.fieldarraytomap.md) + +## IndexPatternsService.fieldArrayToMap property + +Converts field array to map + +Signature: + +```typescript +fieldArrayToMap: (fields: FieldSpec[]) => Record; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.get.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.get.md new file mode 100644 index 0000000000000..4aad6df6b413b --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.get.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [get](./kibana-plugin-plugins-data-public.indexpatternsservice.get.md) + +## IndexPatternsService.get property + +Get an index pattern by id. Cache optimized + +Signature: + +```typescript +get: (id: string) => Promise; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getcache.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getcache.md new file mode 100644 index 0000000000000..ad2a167bd8c74 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getcache.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [getCache](./kibana-plugin-plugins-data-public.indexpatternsservice.getcache.md) + +## IndexPatternsService.getCache property + +Signature: + +```typescript +getCache: () => Promise[] | null | undefined>; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getdefault.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getdefault.md new file mode 100644 index 0000000000000..01d4efeffe921 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getdefault.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [getDefault](./kibana-plugin-plugins-data-public.indexpatternsservice.getdefault.md) + +## IndexPatternsService.getDefault property + +Get default index pattern + +Signature: + +```typescript +getDefault: () => Promise; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforindexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforindexpattern.md new file mode 100644 index 0000000000000..c06c3c6f68492 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforindexpattern.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [getFieldsForIndexPattern](./kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforindexpattern.md) + +## IndexPatternsService.getFieldsForIndexPattern property + +Get field list by providing an index patttern (or spec) + +Signature: + +```typescript +getFieldsForIndexPattern: (indexPattern: IndexPattern | IndexPatternSpec, options?: GetFieldsOptions) => Promise; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforwildcard.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforwildcard.md new file mode 100644 index 0000000000000..aec84866b9e58 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforwildcard.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [getFieldsForWildcard](./kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforwildcard.md) + +## IndexPatternsService.getFieldsForWildcard property + +Get field list by providing { pattern } + +Signature: + +```typescript +getFieldsForWildcard: (options?: GetFieldsOptions) => Promise; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getids.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getids.md new file mode 100644 index 0000000000000..a012e0dc9d9c5 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getids.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [getIds](./kibana-plugin-plugins-data-public.indexpatternsservice.getids.md) + +## IndexPatternsService.getIds property + +Get list of index pattern ids + +Signature: + +```typescript +getIds: (refresh?: boolean) => Promise; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getidswithtitle.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getidswithtitle.md new file mode 100644 index 0000000000000..7d29ced66afa8 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getidswithtitle.md @@ -0,0 +1,16 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [getIdsWithTitle](./kibana-plugin-plugins-data-public.indexpatternsservice.getidswithtitle.md) + +## IndexPatternsService.getIdsWithTitle property + +Get list of index pattern ids with titles + +Signature: + +```typescript +getIdsWithTitle: (refresh?: boolean) => Promise>; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.gettitles.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.gettitles.md new file mode 100644 index 0000000000000..04cc294a79dfc --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.gettitles.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [getTitles](./kibana-plugin-plugins-data-public.indexpatternsservice.gettitles.md) + +## IndexPatternsService.getTitles property + +Get list of index pattern titles + +Signature: + +```typescript +getTitles: (refresh?: boolean) => Promise; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.md new file mode 100644 index 0000000000000..af087344268d7 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.md @@ -0,0 +1,47 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) + +## IndexPatternsService class + +Signature: + +```typescript +export declare class IndexPatternsService +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)({ uiSettings, savedObjectsClient, apiClient, fieldFormats, onNotification, onError, onRedirectNoIndexPattern, })](./kibana-plugin-plugins-data-public.indexpatternsservice._constructor_.md) | | Constructs a new instance of the IndexPatternsService class | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [clearCache](./kibana-plugin-plugins-data-public.indexpatternsservice.clearcache.md) | | (id?: string | undefined) => void | Clear index pattern list cache | +| [ensureDefaultIndexPattern](./kibana-plugin-plugins-data-public.indexpatternsservice.ensuredefaultindexpattern.md) | | EnsureDefaultIndexPattern | | +| [fieldArrayToMap](./kibana-plugin-plugins-data-public.indexpatternsservice.fieldarraytomap.md) | | (fields: FieldSpec[]) => Record<string, FieldSpec> | Converts field array to map | +| [get](./kibana-plugin-plugins-data-public.indexpatternsservice.get.md) | | (id: string) => Promise<IndexPattern> | Get an index pattern by id. Cache optimized | +| [getCache](./kibana-plugin-plugins-data-public.indexpatternsservice.getcache.md) | | () => Promise<SavedObject<IndexPatternSavedObjectAttrs>[] | null | undefined> | | +| [getDefault](./kibana-plugin-plugins-data-public.indexpatternsservice.getdefault.md) | | () => Promise<IndexPattern | null> | Get default index pattern | +| [getFieldsForIndexPattern](./kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforindexpattern.md) | | (indexPattern: IndexPattern | IndexPatternSpec, options?: GetFieldsOptions) => Promise<any> | Get field list by providing an index patttern (or spec) | +| [getFieldsForWildcard](./kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforwildcard.md) | | (options?: GetFieldsOptions) => Promise<any> | Get field list by providing { pattern } | +| [getIds](./kibana-plugin-plugins-data-public.indexpatternsservice.getids.md) | | (refresh?: boolean) => Promise<string[]> | Get list of index pattern ids | +| [getIdsWithTitle](./kibana-plugin-plugins-data-public.indexpatternsservice.getidswithtitle.md) | | (refresh?: boolean) => Promise<Array<{
id: string;
title: string;
}>> | Get list of index pattern ids with titles | +| [getTitles](./kibana-plugin-plugins-data-public.indexpatternsservice.gettitles.md) | | (refresh?: boolean) => Promise<string[]> | Get list of index pattern titles | +| [refreshFields](./kibana-plugin-plugins-data-public.indexpatternsservice.refreshfields.md) | | (indexPattern: IndexPattern) => Promise<void> | Refresh field list for a given index pattern | +| [savedObjectToSpec](./kibana-plugin-plugins-data-public.indexpatternsservice.savedobjecttospec.md) | | (savedObject: SavedObject<IndexPatternAttributes>) => IndexPatternSpec | Converts index pattern saved object to index pattern spec | +| [setDefault](./kibana-plugin-plugins-data-public.indexpatternsservice.setdefault.md) | | (id: string, force?: boolean) => Promise<void> | Optionally set default index pattern, unless force = true | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [create(spec, skipFetchFields)](./kibana-plugin-plugins-data-public.indexpatternsservice.create.md) | | Create a new index pattern instance | +| [createAndSave(spec, override, skipFetchFields)](./kibana-plugin-plugins-data-public.indexpatternsservice.createandsave.md) | | Create a new index pattern and save it right away | +| [createSavedObject(indexPattern, override)](./kibana-plugin-plugins-data-public.indexpatternsservice.createsavedobject.md) | | Save a new index pattern | +| [delete(indexPatternId)](./kibana-plugin-plugins-data-public.indexpatternsservice.delete.md) | | Deletes an index pattern from .kibana index | +| [updateSavedObject(indexPattern, saveAttempts)](./kibana-plugin-plugins-data-public.indexpatternsservice.updatesavedobject.md) | | Save existing index pattern. Will attempt to merge differences if there are conflicts | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.refreshfields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.refreshfields.md new file mode 100644 index 0000000000000..b7c47efbb445a --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.refreshfields.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [refreshFields](./kibana-plugin-plugins-data-public.indexpatternsservice.refreshfields.md) + +## IndexPatternsService.refreshFields property + +Refresh field list for a given index pattern + +Signature: + +```typescript +refreshFields: (indexPattern: IndexPattern) => Promise; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.savedobjecttospec.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.savedobjecttospec.md new file mode 100644 index 0000000000000..7bd40c9cafd42 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.savedobjecttospec.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [savedObjectToSpec](./kibana-plugin-plugins-data-public.indexpatternsservice.savedobjecttospec.md) + +## IndexPatternsService.savedObjectToSpec property + +Converts index pattern saved object to index pattern spec + +Signature: + +```typescript +savedObjectToSpec: (savedObject: SavedObject) => IndexPatternSpec; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.setdefault.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.setdefault.md new file mode 100644 index 0000000000000..2bf8eaa03d1ae --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.setdefault.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [setDefault](./kibana-plugin-plugins-data-public.indexpatternsservice.setdefault.md) + +## IndexPatternsService.setDefault property + +Optionally set default index pattern, unless force = true + +Signature: + +```typescript +setDefault: (id: string, force?: boolean) => Promise; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.updatesavedobject.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.updatesavedobject.md new file mode 100644 index 0000000000000..3973f5d4c3e7b --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.updatesavedobject.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [updateSavedObject](./kibana-plugin-plugins-data-public.indexpatternsservice.updatesavedobject.md) + +## IndexPatternsService.updateSavedObject() method + +Save existing index pattern. Will attempt to merge differences if there are conflicts + +Signature: + +```typescript +updateSavedObject(indexPattern: IndexPattern, saveAttempts?: number): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| indexPattern | IndexPattern | | +| saveAttempts | number | | + +Returns: + +`Promise` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iscompleteresponse.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iscompleteresponse.md new file mode 100644 index 0000000000000..17acf4e0d1be8 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iscompleteresponse.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [isCompleteResponse](./kibana-plugin-plugins-data-public.iscompleteresponse.md) + +## isCompleteResponse variable + +Signature: + +```typescript +isCompleteResponse: (response?: IEsSearchResponse | undefined) => boolean | undefined +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iserrorresponse.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iserrorresponse.md new file mode 100644 index 0000000000000..3f9b1d593870d --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iserrorresponse.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [isErrorResponse](./kibana-plugin-plugins-data-public.iserrorresponse.md) + +## isErrorResponse variable + +Signature: + +```typescript +isErrorResponse: (response?: IEsSearchResponse | undefined) => boolean | undefined +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ispartialresponse.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ispartialresponse.md new file mode 100644 index 0000000000000..9f2f1bbf2f9e0 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ispartialresponse.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [isPartialResponse](./kibana-plugin-plugins-data-public.ispartialresponse.md) + +## isPartialResponse variable + +Signature: + +```typescript +isPartialResponse: (response?: IEsSearchResponse | undefined) => boolean | undefined +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md index f51549c81fb62..8625120d54848 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md @@ -11,11 +11,13 @@ | [AggConfig](./kibana-plugin-plugins-data-public.aggconfig.md) | | | [AggConfigs](./kibana-plugin-plugins-data-public.aggconfigs.md) | | | [AggParamType](./kibana-plugin-plugins-data-public.aggparamtype.md) | | +| [DuplicateIndexPatternError](./kibana-plugin-plugins-data-public.duplicateindexpatternerror.md) | | | [FieldFormat](./kibana-plugin-plugins-data-public.fieldformat.md) | | | [FilterManager](./kibana-plugin-plugins-data-public.filtermanager.md) | | | [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) | | | [IndexPatternField](./kibana-plugin-plugins-data-public.indexpatternfield.md) | | | [IndexPatternSelect](./kibana-plugin-plugins-data-public.indexpatternselect.md) | | +| [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) | | | [OptionedParamType](./kibana-plugin-plugins-data-public.optionedparamtype.md) | | | [Plugin](./kibana-plugin-plugins-data-public.plugin.md) | | | [RequestTimeoutError](./kibana-plugin-plugins-data-public.requesttimeouterror.md) | Class used to signify that a request timed out. Useful for applications to conditionally handle this type of error differently than other errors. | @@ -57,7 +59,6 @@ | [EsQueryConfig](./kibana-plugin-plugins-data-public.esqueryconfig.md) | | | [FieldFormatConfig](./kibana-plugin-plugins-data-public.fieldformatconfig.md) | | | [FieldMappingSpec](./kibana-plugin-plugins-data-public.fieldmappingspec.md) | | -| [Filter](./kibana-plugin-plugins-data-public.filter.md) | | | [IDataPluginServices](./kibana-plugin-plugins-data-public.idatapluginservices.md) | | | [IEsSearchRequest](./kibana-plugin-plugins-data-public.iessearchrequest.md) | | | [IEsSearchResponse](./kibana-plugin-plugins-data-public.iessearchresponse.md) | | @@ -67,7 +68,8 @@ | [IIndexPatternFieldList](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.md) | | | [IKibanaSearchRequest](./kibana-plugin-plugins-data-public.ikibanasearchrequest.md) | | | [IKibanaSearchResponse](./kibana-plugin-plugins-data-public.ikibanasearchresponse.md) | | -| [IndexPatternAttributes](./kibana-plugin-plugins-data-public.indexpatternattributes.md) | Use data plugin interface instead | +| [IndexPatternAttributes](./kibana-plugin-plugins-data-public.indexpatternattributes.md) | | +| [IndexPatternSpec](./kibana-plugin-plugins-data-public.indexpatternspec.md) | | | [IndexPatternTypeMeta](./kibana-plugin-plugins-data-public.indexpatterntypemeta.md) | | | [ISearchOptions](./kibana-plugin-plugins-data-public.isearchoptions.md) | | | [ISearchSetup](./kibana-plugin-plugins-data-public.isearchsetup.md) | The setup contract exposed by the Search plugin exposes the search strategy extension point. | @@ -75,9 +77,9 @@ | [ISearchStartSearchSource](./kibana-plugin-plugins-data-public.isearchstartsearchsource.md) | high level search service | | [KueryNode](./kibana-plugin-plugins-data-public.kuerynode.md) | | | [OptionedValueProp](./kibana-plugin-plugins-data-public.optionedvalueprop.md) | | -| [Query](./kibana-plugin-plugins-data-public.query.md) | | | [QueryState](./kibana-plugin-plugins-data-public.querystate.md) | All query state service state | | [QueryStateChange](./kibana-plugin-plugins-data-public.querystatechange.md) | | +| [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) | | | [QuerySuggestionBasic](./kibana-plugin-plugins-data-public.querysuggestionbasic.md) | \* | | [QuerySuggestionField](./kibana-plugin-plugins-data-public.querysuggestionfield.md) | \* | | [QuerySuggestionGetFnArgs](./kibana-plugin-plugins-data-public.querysuggestiongetfnargs.md) | \* | @@ -90,7 +92,6 @@ | [SearchSourceFields](./kibana-plugin-plugins-data-public.searchsourcefields.md) | search source fields | | [TabbedAggColumn](./kibana-plugin-plugins-data-public.tabbedaggcolumn.md) | \* | | [TabbedTable](./kibana-plugin-plugins-data-public.tabbedtable.md) | \* | -| [TimeRange](./kibana-plugin-plugins-data-public.timerange.md) | | ## Variables @@ -115,8 +116,11 @@ | [getKbnTypeNames](./kibana-plugin-plugins-data-public.getkbntypenames.md) | Get the esTypes known by all kbnFieldTypes {Array} | | [indexPatterns](./kibana-plugin-plugins-data-public.indexpatterns.md) | | | [injectSearchSourceReferences](./kibana-plugin-plugins-data-public.injectsearchsourcereferences.md) | | +| [isCompleteResponse](./kibana-plugin-plugins-data-public.iscompleteresponse.md) | | +| [isErrorResponse](./kibana-plugin-plugins-data-public.iserrorresponse.md) | | | [isFilter](./kibana-plugin-plugins-data-public.isfilter.md) | | | [isFilters](./kibana-plugin-plugins-data-public.isfilters.md) | | +| [isPartialResponse](./kibana-plugin-plugins-data-public.ispartialresponse.md) | | | [isQuery](./kibana-plugin-plugins-data-public.isquery.md) | | | [isTimeRange](./kibana-plugin-plugins-data-public.istimerange.md) | | | [parseSearchSourceJSON](./kibana-plugin-plugins-data-public.parsesearchsourcejson.md) | | @@ -145,6 +149,7 @@ | [FieldFormatsContentType](./kibana-plugin-plugins-data-public.fieldformatscontenttype.md) | \* | | [FieldFormatsGetConfigFn](./kibana-plugin-plugins-data-public.fieldformatsgetconfigfn.md) | | | [FieldFormatsStart](./kibana-plugin-plugins-data-public.fieldformatsstart.md) | | +| [Filter](./kibana-plugin-plugins-data-public.filter.md) | | | [IAggConfig](./kibana-plugin-plugins-data-public.iaggconfig.md) | AggConfig This class represents an aggregation, which is displayed in the left-hand nav of the Visualize app. | | [IAggType](./kibana-plugin-plugins-data-public.iaggtype.md) | | | [IFieldFormat](./kibana-plugin-plugins-data-public.ifieldformat.md) | | @@ -162,6 +167,7 @@ | [ParsedInterval](./kibana-plugin-plugins-data-public.parsedinterval.md) | | | [PhraseFilter](./kibana-plugin-plugins-data-public.phrasefilter.md) | | | [PhrasesFilter](./kibana-plugin-plugins-data-public.phrasesfilter.md) | | +| [Query](./kibana-plugin-plugins-data-public.query.md) | | | [QueryStart](./kibana-plugin-plugins-data-public.querystart.md) | | | [QuerySuggestion](./kibana-plugin-plugins-data-public.querysuggestion.md) | \* | | [QuerySuggestionGetFn](./kibana-plugin-plugins-data-public.querysuggestiongetfn.md) | | @@ -173,4 +179,5 @@ | [TabbedAggRow](./kibana-plugin-plugins-data-public.tabbedaggrow.md) | \* | | [TimefilterContract](./kibana-plugin-plugins-data-public.timefiltercontract.md) | | | [TimeHistoryContract](./kibana-plugin-plugins-data-public.timehistorycontract.md) | | +| [TimeRange](./kibana-plugin-plugins-data-public.timerange.md) | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md index e85747b8cc3d7..aa7c3bb5d4932 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md @@ -7,5 +7,5 @@ Signature: ```typescript -QueryStringInput: React.FC> +QueryStringInput: React.FC ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.bubblesubmitevent.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.bubblesubmitevent.md new file mode 100644 index 0000000000000..5a41852001ac0 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.bubblesubmitevent.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [bubbleSubmitEvent](./kibana-plugin-plugins-data-public.querystringinputprops.bubblesubmitevent.md) + +## QueryStringInputProps.bubbleSubmitEvent property + +Signature: + +```typescript +bubbleSubmitEvent?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.classname.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.classname.md new file mode 100644 index 0000000000000..7fa3b76977183 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.classname.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [className](./kibana-plugin-plugins-data-public.querystringinputprops.classname.md) + +## QueryStringInputProps.className property + +Signature: + +```typescript +className?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.datatestsubj.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.datatestsubj.md new file mode 100644 index 0000000000000..edaedf49f4b10 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.datatestsubj.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [dataTestSubj](./kibana-plugin-plugins-data-public.querystringinputprops.datatestsubj.md) + +## QueryStringInputProps.dataTestSubj property + +Signature: + +```typescript +dataTestSubj?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.disableautofocus.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.disableautofocus.md new file mode 100644 index 0000000000000..cc4c6f606409e --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.disableautofocus.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [disableAutoFocus](./kibana-plugin-plugins-data-public.querystringinputprops.disableautofocus.md) + +## QueryStringInputProps.disableAutoFocus property + +Signature: + +```typescript +disableAutoFocus?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.indexpatterns.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.indexpatterns.md new file mode 100644 index 0000000000000..3783138696020 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.indexpatterns.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [indexPatterns](./kibana-plugin-plugins-data-public.querystringinputprops.indexpatterns.md) + +## QueryStringInputProps.indexPatterns property + +Signature: + +```typescript +indexPatterns: Array; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.isinvalid.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.isinvalid.md new file mode 100644 index 0000000000000..a282ac3bc5049 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.isinvalid.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [isInvalid](./kibana-plugin-plugins-data-public.querystringinputprops.isinvalid.md) + +## QueryStringInputProps.isInvalid property + +Signature: + +```typescript +isInvalid?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.languageswitcherpopoveranchorposition.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.languageswitcherpopoveranchorposition.md new file mode 100644 index 0000000000000..d133a0930b53d --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.languageswitcherpopoveranchorposition.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [languageSwitcherPopoverAnchorPosition](./kibana-plugin-plugins-data-public.querystringinputprops.languageswitcherpopoveranchorposition.md) + +## QueryStringInputProps.languageSwitcherPopoverAnchorPosition property + +Signature: + +```typescript +languageSwitcherPopoverAnchorPosition?: PopoverAnchorPosition; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.md new file mode 100644 index 0000000000000..d503980da7947 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.md @@ -0,0 +1,34 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) + +## QueryStringInputProps interface + +Signature: + +```typescript +export interface QueryStringInputProps +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [bubbleSubmitEvent](./kibana-plugin-plugins-data-public.querystringinputprops.bubblesubmitevent.md) | boolean | | +| [className](./kibana-plugin-plugins-data-public.querystringinputprops.classname.md) | string | | +| [dataTestSubj](./kibana-plugin-plugins-data-public.querystringinputprops.datatestsubj.md) | string | | +| [disableAutoFocus](./kibana-plugin-plugins-data-public.querystringinputprops.disableautofocus.md) | boolean | | +| [indexPatterns](./kibana-plugin-plugins-data-public.querystringinputprops.indexpatterns.md) | Array<IIndexPattern | string> | | +| [isInvalid](./kibana-plugin-plugins-data-public.querystringinputprops.isinvalid.md) | boolean | | +| [languageSwitcherPopoverAnchorPosition](./kibana-plugin-plugins-data-public.querystringinputprops.languageswitcherpopoveranchorposition.md) | PopoverAnchorPosition | | +| [onBlur](./kibana-plugin-plugins-data-public.querystringinputprops.onblur.md) | () => void | | +| [onChange](./kibana-plugin-plugins-data-public.querystringinputprops.onchange.md) | (query: Query) => void | | +| [onChangeQueryInputFocus](./kibana-plugin-plugins-data-public.querystringinputprops.onchangequeryinputfocus.md) | (isFocused: boolean) => void | | +| [onSubmit](./kibana-plugin-plugins-data-public.querystringinputprops.onsubmit.md) | (query: Query) => void | | +| [persistedLog](./kibana-plugin-plugins-data-public.querystringinputprops.persistedlog.md) | PersistedLog | | +| [placeholder](./kibana-plugin-plugins-data-public.querystringinputprops.placeholder.md) | string | | +| [prepend](./kibana-plugin-plugins-data-public.querystringinputprops.prepend.md) | any | | +| [query](./kibana-plugin-plugins-data-public.querystringinputprops.query.md) | Query | | +| [screenTitle](./kibana-plugin-plugins-data-public.querystringinputprops.screentitle.md) | string | | +| [size](./kibana-plugin-plugins-data-public.querystringinputprops.size.md) | SuggestionsListSize | | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.onblur.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.onblur.md new file mode 100644 index 0000000000000..10f2ae2ea4f14 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.onblur.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [onBlur](./kibana-plugin-plugins-data-public.querystringinputprops.onblur.md) + +## QueryStringInputProps.onBlur property + +Signature: + +```typescript +onBlur?: () => void; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.onchange.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.onchange.md new file mode 100644 index 0000000000000..fee44d7afd506 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.onchange.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [onChange](./kibana-plugin-plugins-data-public.querystringinputprops.onchange.md) + +## QueryStringInputProps.onChange property + +Signature: + +```typescript +onChange?: (query: Query) => void; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.onchangequeryinputfocus.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.onchangequeryinputfocus.md new file mode 100644 index 0000000000000..0421ae9c8bac5 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.onchangequeryinputfocus.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [onChangeQueryInputFocus](./kibana-plugin-plugins-data-public.querystringinputprops.onchangequeryinputfocus.md) + +## QueryStringInputProps.onChangeQueryInputFocus property + +Signature: + +```typescript +onChangeQueryInputFocus?: (isFocused: boolean) => void; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.onsubmit.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.onsubmit.md new file mode 100644 index 0000000000000..951ec7419485f --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.onsubmit.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [onSubmit](./kibana-plugin-plugins-data-public.querystringinputprops.onsubmit.md) + +## QueryStringInputProps.onSubmit property + +Signature: + +```typescript +onSubmit?: (query: Query) => void; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.persistedlog.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.persistedlog.md new file mode 100644 index 0000000000000..d1a8efb364016 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.persistedlog.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [persistedLog](./kibana-plugin-plugins-data-public.querystringinputprops.persistedlog.md) + +## QueryStringInputProps.persistedLog property + +Signature: + +```typescript +persistedLog?: PersistedLog; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.placeholder.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.placeholder.md new file mode 100644 index 0000000000000..31e41f4d55205 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.placeholder.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [placeholder](./kibana-plugin-plugins-data-public.querystringinputprops.placeholder.md) + +## QueryStringInputProps.placeholder property + +Signature: + +```typescript +placeholder?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.prepend.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.prepend.md new file mode 100644 index 0000000000000..7be882058d3fd --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.prepend.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [prepend](./kibana-plugin-plugins-data-public.querystringinputprops.prepend.md) + +## QueryStringInputProps.prepend property + +Signature: + +```typescript +prepend?: any; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.query.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.query.md new file mode 100644 index 0000000000000..f15f6d082332b --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.query.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [query](./kibana-plugin-plugins-data-public.querystringinputprops.query.md) + +## QueryStringInputProps.query property + +Signature: + +```typescript +query: Query; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.screentitle.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.screentitle.md new file mode 100644 index 0000000000000..0c80252d74571 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.screentitle.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [screenTitle](./kibana-plugin-plugins-data-public.querystringinputprops.screentitle.md) + +## QueryStringInputProps.screenTitle property + +Signature: + +```typescript +screenTitle?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.size.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.size.md new file mode 100644 index 0000000000000..6b0e53a23e07b --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinputprops.size.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [QueryStringInputProps](./kibana-plugin-plugins-data-public.querystringinputprops.md) > [size](./kibana-plugin-plugins-data-public.querystringinputprops.size.md) + +## QueryStringInputProps.size property + +Signature: + +```typescript +size?: SuggestionsListSize; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.aggregatable.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.aggregatable.md new file mode 100644 index 0000000000000..2889ee34ad77b --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.aggregatable.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [FieldDescriptor](./kibana-plugin-plugins-data-server.fielddescriptor.md) > [aggregatable](./kibana-plugin-plugins-data-server.fielddescriptor.aggregatable.md) + +## FieldDescriptor.aggregatable property + +Signature: + +```typescript +aggregatable: boolean; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.estypes.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.estypes.md new file mode 100644 index 0000000000000..9caa374d8da48 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.estypes.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [FieldDescriptor](./kibana-plugin-plugins-data-server.fielddescriptor.md) > [esTypes](./kibana-plugin-plugins-data-server.fielddescriptor.estypes.md) + +## FieldDescriptor.esTypes property + +Signature: + +```typescript +esTypes: string[]; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.md new file mode 100644 index 0000000000000..693de675da940 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [FieldDescriptor](./kibana-plugin-plugins-data-server.fielddescriptor.md) + +## FieldDescriptor interface + +Signature: + +```typescript +export interface FieldDescriptor +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [aggregatable](./kibana-plugin-plugins-data-server.fielddescriptor.aggregatable.md) | boolean | | +| [esTypes](./kibana-plugin-plugins-data-server.fielddescriptor.estypes.md) | string[] | | +| [name](./kibana-plugin-plugins-data-server.fielddescriptor.name.md) | string | | +| [readFromDocValues](./kibana-plugin-plugins-data-server.fielddescriptor.readfromdocvalues.md) | boolean | | +| [searchable](./kibana-plugin-plugins-data-server.fielddescriptor.searchable.md) | boolean | | +| [subType](./kibana-plugin-plugins-data-server.fielddescriptor.subtype.md) | FieldSubType | | +| [type](./kibana-plugin-plugins-data-server.fielddescriptor.type.md) | string | | + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.name.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.name.md new file mode 100644 index 0000000000000..178880a34cd4d --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.name.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [FieldDescriptor](./kibana-plugin-plugins-data-server.fielddescriptor.md) > [name](./kibana-plugin-plugins-data-server.fielddescriptor.name.md) + +## FieldDescriptor.name property + +Signature: + +```typescript +name: string; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.readfromdocvalues.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.readfromdocvalues.md new file mode 100644 index 0000000000000..b60dc5d0dfed0 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.readfromdocvalues.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [FieldDescriptor](./kibana-plugin-plugins-data-server.fielddescriptor.md) > [readFromDocValues](./kibana-plugin-plugins-data-server.fielddescriptor.readfromdocvalues.md) + +## FieldDescriptor.readFromDocValues property + +Signature: + +```typescript +readFromDocValues: boolean; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.searchable.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.searchable.md new file mode 100644 index 0000000000000..efc7b4219a355 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.searchable.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [FieldDescriptor](./kibana-plugin-plugins-data-server.fielddescriptor.md) > [searchable](./kibana-plugin-plugins-data-server.fielddescriptor.searchable.md) + +## FieldDescriptor.searchable property + +Signature: + +```typescript +searchable: boolean; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.subtype.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.subtype.md new file mode 100644 index 0000000000000..b08179f12f250 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.subtype.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [FieldDescriptor](./kibana-plugin-plugins-data-server.fielddescriptor.md) > [subType](./kibana-plugin-plugins-data-server.fielddescriptor.subtype.md) + +## FieldDescriptor.subType property + +Signature: + +```typescript +subType?: FieldSubType; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.type.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.type.md new file mode 100644 index 0000000000000..7b0513a60c90e --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fielddescriptor.type.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [FieldDescriptor](./kibana-plugin-plugins-data-server.fielddescriptor.md) > [type](./kibana-plugin-plugins-data-server.fielddescriptor.type.md) + +## FieldDescriptor.type property + +Signature: + +```typescript +type: string; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.fieldformatmap.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.fieldformatmap.md deleted file mode 100644 index ab9e3171d7d7b..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.fieldformatmap.md +++ /dev/null @@ -1,14 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IIndexPattern](./kibana-plugin-plugins-data-server.iindexpattern.md) > [fieldFormatMap](./kibana-plugin-plugins-data-server.iindexpattern.fieldformatmap.md) - -## IIndexPattern.fieldFormatMap property - -Signature: - -```typescript -fieldFormatMap?: Record; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.fields.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.fields.md deleted file mode 100644 index fb6d046ff2174..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.fields.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IIndexPattern](./kibana-plugin-plugins-data-server.iindexpattern.md) > [fields](./kibana-plugin-plugins-data-server.iindexpattern.fields.md) - -## IIndexPattern.fields property - -Signature: - -```typescript -fields: IFieldType[]; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.gettimefield.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.gettimefield.md deleted file mode 100644 index a4d6abcf86a94..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.gettimefield.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IIndexPattern](./kibana-plugin-plugins-data-server.iindexpattern.md) > [getTimeField](./kibana-plugin-plugins-data-server.iindexpattern.gettimefield.md) - -## IIndexPattern.getTimeField() method - -Signature: - -```typescript -getTimeField?(): IFieldType | undefined; -``` -Returns: - -`IFieldType | undefined` - diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.id.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.id.md deleted file mode 100644 index cac263df0f9aa..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.id.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IIndexPattern](./kibana-plugin-plugins-data-server.iindexpattern.md) > [id](./kibana-plugin-plugins-data-server.iindexpattern.id.md) - -## IIndexPattern.id property - -Signature: - -```typescript -id?: string; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.md deleted file mode 100644 index a79244a24acf5..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.md +++ /dev/null @@ -1,29 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IIndexPattern](./kibana-plugin-plugins-data-server.iindexpattern.md) - -## IIndexPattern interface - -Signature: - -```typescript -export interface IIndexPattern -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [fieldFormatMap](./kibana-plugin-plugins-data-server.iindexpattern.fieldformatmap.md) | Record<string, {
id: string;
params: unknown;
}> | | -| [fields](./kibana-plugin-plugins-data-server.iindexpattern.fields.md) | IFieldType[] | | -| [id](./kibana-plugin-plugins-data-server.iindexpattern.id.md) | string | | -| [timeFieldName](./kibana-plugin-plugins-data-server.iindexpattern.timefieldname.md) | string | | -| [title](./kibana-plugin-plugins-data-server.iindexpattern.title.md) | string | | -| [type](./kibana-plugin-plugins-data-server.iindexpattern.type.md) | string | | - -## Methods - -| Method | Description | -| --- | --- | -| [getTimeField()](./kibana-plugin-plugins-data-server.iindexpattern.gettimefield.md) | | - diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.timefieldname.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.timefieldname.md deleted file mode 100644 index 14cf514477da4..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.timefieldname.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IIndexPattern](./kibana-plugin-plugins-data-server.iindexpattern.md) > [timeFieldName](./kibana-plugin-plugins-data-server.iindexpattern.timefieldname.md) - -## IIndexPattern.timeFieldName property - -Signature: - -```typescript -timeFieldName?: string; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.title.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.title.md deleted file mode 100644 index 119963d7ff95d..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.title.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IIndexPattern](./kibana-plugin-plugins-data-server.iindexpattern.md) > [title](./kibana-plugin-plugins-data-server.iindexpattern.title.md) - -## IIndexPattern.title property - -Signature: - -```typescript -title: string; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.type.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.type.md deleted file mode 100644 index 6b89b71664b23..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.type.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IIndexPattern](./kibana-plugin-plugins-data-server.iindexpattern.md) > [type](./kibana-plugin-plugins-data-server.iindexpattern.type.md) - -## IIndexPattern.type property - -Signature: - -```typescript -type?: string; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern._constructor_.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern._constructor_.md new file mode 100644 index 0000000000000..f7f8e51c4b632 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern._constructor_.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [(constructor)](./kibana-plugin-plugins-data-server.indexpattern._constructor_.md) + +## IndexPattern.(constructor) + +Constructs a new instance of the `IndexPattern` class + +Signature: + +```typescript +constructor({ spec, savedObjectsClient, fieldFormats, shortDotsEnable, metaFields, }: IndexPatternDeps); +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| { spec, savedObjectsClient, fieldFormats, shortDotsEnable, metaFields, } | IndexPatternDeps | | + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.addscriptedfield.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.addscriptedfield.md new file mode 100644 index 0000000000000..6d206e88b5b56 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.addscriptedfield.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [addScriptedField](./kibana-plugin-plugins-data-server.indexpattern.addscriptedfield.md) + +## IndexPattern.addScriptedField() method + +Add scripted field to field list + +Signature: + +```typescript +addScriptedField(name: string, script: string, fieldType?: string, lang?: string): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| name | string | | +| script | string | | +| fieldType | string | | +| lang | string | | + +Returns: + +`Promise` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.fieldformatmap.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.fieldformatmap.md new file mode 100644 index 0000000000000..2f686bd313d58 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.fieldformatmap.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [fieldFormatMap](./kibana-plugin-plugins-data-server.indexpattern.fieldformatmap.md) + +## IndexPattern.fieldFormatMap property + +Signature: + +```typescript +fieldFormatMap: Record; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.fields.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.fields.md new file mode 100644 index 0000000000000..5b22014486c02 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.fields.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [fields](./kibana-plugin-plugins-data-server.indexpattern.fields.md) + +## IndexPattern.fields property + +Signature: + +```typescript +fields: IIndexPatternFieldList & { + toSpec: () => IndexPatternFieldMap; + }; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.flattenhit.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.flattenhit.md new file mode 100644 index 0000000000000..33c6dedc6dcd8 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.flattenhit.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [flattenHit](./kibana-plugin-plugins-data-server.indexpattern.flattenhit.md) + +## IndexPattern.flattenHit property + +Signature: + +```typescript +flattenHit: (hit: Record, deep?: boolean) => Record; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.formatfield.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.formatfield.md new file mode 100644 index 0000000000000..07db8a0805b07 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.formatfield.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [formatField](./kibana-plugin-plugins-data-server.indexpattern.formatfield.md) + +## IndexPattern.formatField property + +Signature: + +```typescript +formatField: FormatFieldFn; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.formathit.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.formathit.md new file mode 100644 index 0000000000000..75f282a8991fc --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.formathit.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [formatHit](./kibana-plugin-plugins-data-server.indexpattern.formathit.md) + +## IndexPattern.formatHit property + +Signature: + +```typescript +formatHit: { + (hit: Record, type?: string): any; + formatField: FormatFieldFn; + }; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getaggregationrestrictions.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getaggregationrestrictions.md new file mode 100644 index 0000000000000..b655e779e4fa4 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getaggregationrestrictions.md @@ -0,0 +1,29 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [getAggregationRestrictions](./kibana-plugin-plugins-data-server.indexpattern.getaggregationrestrictions.md) + +## IndexPattern.getAggregationRestrictions() method + +Signature: + +```typescript +getAggregationRestrictions(): Record> | undefined; +``` +Returns: + +`Record> | undefined` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getassavedobjectbody.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getassavedobjectbody.md new file mode 100644 index 0000000000000..f1bdb2f729414 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getassavedobjectbody.md @@ -0,0 +1,35 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [getAsSavedObjectBody](./kibana-plugin-plugins-data-server.indexpattern.getassavedobjectbody.md) + +## IndexPattern.getAsSavedObjectBody() method + +Returns index pattern as saved object body for saving + +Signature: + +```typescript +getAsSavedObjectBody(): { + title: string; + timeFieldName: string | undefined; + intervalName: string | undefined; + sourceFilters: string | undefined; + fields: string | undefined; + fieldFormatMap: string | undefined; + type: string | undefined; + typeMeta: string | undefined; + }; +``` +Returns: + +`{ + title: string; + timeFieldName: string | undefined; + intervalName: string | undefined; + sourceFilters: string | undefined; + fields: string | undefined; + fieldFormatMap: string | undefined; + type: string | undefined; + typeMeta: string | undefined; + }` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getcomputedfields.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getcomputedfields.md new file mode 100644 index 0000000000000..eab6ae9bf9033 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getcomputedfields.md @@ -0,0 +1,29 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [getComputedFields](./kibana-plugin-plugins-data-server.indexpattern.getcomputedfields.md) + +## IndexPattern.getComputedFields() method + +Signature: + +```typescript +getComputedFields(): { + storedFields: string[]; + scriptFields: any; + docvalueFields: { + field: any; + format: string; + }[]; + }; +``` +Returns: + +`{ + storedFields: string[]; + scriptFields: any; + docvalueFields: { + field: any; + format: string; + }[]; + }` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getfieldbyname.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getfieldbyname.md new file mode 100644 index 0000000000000..712be3b72828a --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getfieldbyname.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [getFieldByName](./kibana-plugin-plugins-data-server.indexpattern.getfieldbyname.md) + +## IndexPattern.getFieldByName() method + +Signature: + +```typescript +getFieldByName(name: string): IndexPatternField | undefined; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| name | string | | + +Returns: + +`IndexPatternField | undefined` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getformatterforfield.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getformatterforfield.md new file mode 100644 index 0000000000000..7dc2756009f4e --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getformatterforfield.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [getFormatterForField](./kibana-plugin-plugins-data-server.indexpattern.getformatterforfield.md) + +## IndexPattern.getFormatterForField() method + +Provide a field, get its formatter + +Signature: + +```typescript +getFormatterForField(field: IndexPatternField | IndexPatternField['spec'] | IFieldType): FieldFormat; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| field | IndexPatternField | IndexPatternField['spec'] | IFieldType | | + +Returns: + +`FieldFormat` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getnonscriptedfields.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getnonscriptedfields.md new file mode 100644 index 0000000000000..89d79d9b750fa --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getnonscriptedfields.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [getNonScriptedFields](./kibana-plugin-plugins-data-server.indexpattern.getnonscriptedfields.md) + +## IndexPattern.getNonScriptedFields() method + +Signature: + +```typescript +getNonScriptedFields(): IndexPatternField[]; +``` +Returns: + +`IndexPatternField[]` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getoriginalsavedobjectbody.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getoriginalsavedobjectbody.md new file mode 100644 index 0000000000000..324f9d0152ab5 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getoriginalsavedobjectbody.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [getOriginalSavedObjectBody](./kibana-plugin-plugins-data-server.indexpattern.getoriginalsavedobjectbody.md) + +## IndexPattern.getOriginalSavedObjectBody property + +Get last saved saved object fields + +Signature: + +```typescript +getOriginalSavedObjectBody: () => { + title?: string | undefined; + timeFieldName?: string | undefined; + intervalName?: string | undefined; + fields?: string | undefined; + sourceFilters?: string | undefined; + fieldFormatMap?: string | undefined; + typeMeta?: string | undefined; + type?: string | undefined; + }; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getscriptedfields.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getscriptedfields.md new file mode 100644 index 0000000000000..edfff8ec5efac --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getscriptedfields.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [getScriptedFields](./kibana-plugin-plugins-data-server.indexpattern.getscriptedfields.md) + +## IndexPattern.getScriptedFields() method + +Signature: + +```typescript +getScriptedFields(): IndexPatternField[]; +``` +Returns: + +`IndexPatternField[]` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getsourcefiltering.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getsourcefiltering.md new file mode 100644 index 0000000000000..240f9b4fb0aa2 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getsourcefiltering.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [getSourceFiltering](./kibana-plugin-plugins-data-server.indexpattern.getsourcefiltering.md) + +## IndexPattern.getSourceFiltering() method + +Get the source filtering configuration for that index. + +Signature: + +```typescript +getSourceFiltering(): { + excludes: any[]; + }; +``` +Returns: + +`{ + excludes: any[]; + }` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.gettimefield.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.gettimefield.md new file mode 100644 index 0000000000000..b5806f883fb9f --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.gettimefield.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [getTimeField](./kibana-plugin-plugins-data-server.indexpattern.gettimefield.md) + +## IndexPattern.getTimeField() method + +Signature: + +```typescript +getTimeField(): IndexPatternField | undefined; +``` +Returns: + +`IndexPatternField | undefined` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.id.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.id.md new file mode 100644 index 0000000000000..8fad82bd06705 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.id.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [id](./kibana-plugin-plugins-data-server.indexpattern.id.md) + +## IndexPattern.id property + +Signature: + +```typescript +id?: string; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.intervalname.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.intervalname.md new file mode 100644 index 0000000000000..caaa6929235f8 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.intervalname.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [intervalName](./kibana-plugin-plugins-data-server.indexpattern.intervalname.md) + +## IndexPattern.intervalName property + +Signature: + +```typescript +intervalName: string | undefined; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.istimebased.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.istimebased.md new file mode 100644 index 0000000000000..790744979942d --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.istimebased.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [isTimeBased](./kibana-plugin-plugins-data-server.indexpattern.istimebased.md) + +## IndexPattern.isTimeBased() method + +Signature: + +```typescript +isTimeBased(): boolean; +``` +Returns: + +`boolean` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.istimebasedwildcard.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.istimebasedwildcard.md new file mode 100644 index 0000000000000..7ef5e8318040a --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.istimebasedwildcard.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [isTimeBasedWildcard](./kibana-plugin-plugins-data-server.indexpattern.istimebasedwildcard.md) + +## IndexPattern.isTimeBasedWildcard() method + +Signature: + +```typescript +isTimeBasedWildcard(): boolean; +``` +Returns: + +`boolean` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.istimenanosbased.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.istimenanosbased.md new file mode 100644 index 0000000000000..22fb60eba4f6e --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.istimenanosbased.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [isTimeNanosBased](./kibana-plugin-plugins-data-server.indexpattern.istimenanosbased.md) + +## IndexPattern.isTimeNanosBased() method + +Signature: + +```typescript +isTimeNanosBased(): boolean; +``` +Returns: + +`boolean` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md new file mode 100644 index 0000000000000..d877854444a09 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md @@ -0,0 +1,60 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) + +## IndexPattern class + +Signature: + +```typescript +export declare class IndexPattern implements IIndexPattern +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)({ spec, savedObjectsClient, fieldFormats, shortDotsEnable, metaFields, })](./kibana-plugin-plugins-data-server.indexpattern._constructor_.md) | | Constructs a new instance of the IndexPattern class | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [fieldFormatMap](./kibana-plugin-plugins-data-server.indexpattern.fieldformatmap.md) | | Record<string, any> | | +| [fields](./kibana-plugin-plugins-data-server.indexpattern.fields.md) | | IIndexPatternFieldList & {
toSpec: () => IndexPatternFieldMap;
} | | +| [flattenHit](./kibana-plugin-plugins-data-server.indexpattern.flattenhit.md) | | (hit: Record<string, any>, deep?: boolean) => Record<string, any> | | +| [formatField](./kibana-plugin-plugins-data-server.indexpattern.formatfield.md) | | FormatFieldFn | | +| [formatHit](./kibana-plugin-plugins-data-server.indexpattern.formathit.md) | | {
(hit: Record<string, any>, type?: string): any;
formatField: FormatFieldFn;
} | | +| [getOriginalSavedObjectBody](./kibana-plugin-plugins-data-server.indexpattern.getoriginalsavedobjectbody.md) | | () => {
title?: string | undefined;
timeFieldName?: string | undefined;
intervalName?: string | undefined;
fields?: string | undefined;
sourceFilters?: string | undefined;
fieldFormatMap?: string | undefined;
typeMeta?: string | undefined;
type?: string | undefined;
} | Get last saved saved object fields | +| [id](./kibana-plugin-plugins-data-server.indexpattern.id.md) | | string | | +| [intervalName](./kibana-plugin-plugins-data-server.indexpattern.intervalname.md) | | string | undefined | | +| [metaFields](./kibana-plugin-plugins-data-server.indexpattern.metafields.md) | | string[] | | +| [resetOriginalSavedObjectBody](./kibana-plugin-plugins-data-server.indexpattern.resetoriginalsavedobjectbody.md) | | () => void | Reset last saved saved object fields. used after saving | +| [sourceFilters](./kibana-plugin-plugins-data-server.indexpattern.sourcefilters.md) | | SourceFilter[] | | +| [timeFieldName](./kibana-plugin-plugins-data-server.indexpattern.timefieldname.md) | | string | undefined | | +| [title](./kibana-plugin-plugins-data-server.indexpattern.title.md) | | string | | +| [type](./kibana-plugin-plugins-data-server.indexpattern.type.md) | | string | undefined | | +| [typeMeta](./kibana-plugin-plugins-data-server.indexpattern.typemeta.md) | | TypeMeta | | +| [version](./kibana-plugin-plugins-data-server.indexpattern.version.md) | | string | undefined | | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [addScriptedField(name, script, fieldType, lang)](./kibana-plugin-plugins-data-server.indexpattern.addscriptedfield.md) | | Add scripted field to field list | +| [getAggregationRestrictions()](./kibana-plugin-plugins-data-server.indexpattern.getaggregationrestrictions.md) | | | +| [getAsSavedObjectBody()](./kibana-plugin-plugins-data-server.indexpattern.getassavedobjectbody.md) | | Returns index pattern as saved object body for saving | +| [getComputedFields()](./kibana-plugin-plugins-data-server.indexpattern.getcomputedfields.md) | | | +| [getFieldByName(name)](./kibana-plugin-plugins-data-server.indexpattern.getfieldbyname.md) | | | +| [getFormatterForField(field)](./kibana-plugin-plugins-data-server.indexpattern.getformatterforfield.md) | | Provide a field, get its formatter | +| [getNonScriptedFields()](./kibana-plugin-plugins-data-server.indexpattern.getnonscriptedfields.md) | | | +| [getScriptedFields()](./kibana-plugin-plugins-data-server.indexpattern.getscriptedfields.md) | | | +| [getSourceFiltering()](./kibana-plugin-plugins-data-server.indexpattern.getsourcefiltering.md) | | Get the source filtering configuration for that index. | +| [getTimeField()](./kibana-plugin-plugins-data-server.indexpattern.gettimefield.md) | | | +| [isTimeBased()](./kibana-plugin-plugins-data-server.indexpattern.istimebased.md) | | | +| [isTimeBasedWildcard()](./kibana-plugin-plugins-data-server.indexpattern.istimebasedwildcard.md) | | | +| [isTimeNanosBased()](./kibana-plugin-plugins-data-server.indexpattern.istimenanosbased.md) | | | +| [popularizeField(fieldName, unit)](./kibana-plugin-plugins-data-server.indexpattern.popularizefield.md) | | | +| [removeScriptedField(fieldName)](./kibana-plugin-plugins-data-server.indexpattern.removescriptedfield.md) | | Remove scripted field from field list | +| [toSpec()](./kibana-plugin-plugins-data-server.indexpattern.tospec.md) | | | + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.metafields.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.metafields.md new file mode 100644 index 0000000000000..a2c7c806d6057 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.metafields.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [metaFields](./kibana-plugin-plugins-data-server.indexpattern.metafields.md) + +## IndexPattern.metaFields property + +Signature: + +```typescript +metaFields: string[]; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.popularizefield.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.popularizefield.md new file mode 100644 index 0000000000000..8b2c9242a6256 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.popularizefield.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [popularizeField](./kibana-plugin-plugins-data-server.indexpattern.popularizefield.md) + +## IndexPattern.popularizeField() method + +Signature: + +```typescript +popularizeField(fieldName: string, unit?: number): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | | +| unit | number | | + +Returns: + +`Promise` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.removescriptedfield.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.removescriptedfield.md new file mode 100644 index 0000000000000..3162a7f42dd12 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.removescriptedfield.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [removeScriptedField](./kibana-plugin-plugins-data-server.indexpattern.removescriptedfield.md) + +## IndexPattern.removeScriptedField() method + +Remove scripted field from field list + +Signature: + +```typescript +removeScriptedField(fieldName: string): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| fieldName | string | | + +Returns: + +`void` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.resetoriginalsavedobjectbody.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.resetoriginalsavedobjectbody.md new file mode 100644 index 0000000000000..18ec7070bd577 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.resetoriginalsavedobjectbody.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [resetOriginalSavedObjectBody](./kibana-plugin-plugins-data-server.indexpattern.resetoriginalsavedobjectbody.md) + +## IndexPattern.resetOriginalSavedObjectBody property + +Reset last saved saved object fields. used after saving + +Signature: + +```typescript +resetOriginalSavedObjectBody: () => void; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.sourcefilters.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.sourcefilters.md new file mode 100644 index 0000000000000..d359bef2f30a9 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.sourcefilters.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [sourceFilters](./kibana-plugin-plugins-data-server.indexpattern.sourcefilters.md) + +## IndexPattern.sourceFilters property + +Signature: + +```typescript +sourceFilters?: SourceFilter[]; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.timefieldname.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.timefieldname.md new file mode 100644 index 0000000000000..35740afa4e3dc --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.timefieldname.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [timeFieldName](./kibana-plugin-plugins-data-server.indexpattern.timefieldname.md) + +## IndexPattern.timeFieldName property + +Signature: + +```typescript +timeFieldName: string | undefined; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.title.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.title.md new file mode 100644 index 0000000000000..4cebde989aebd --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.title.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [title](./kibana-plugin-plugins-data-server.indexpattern.title.md) + +## IndexPattern.title property + +Signature: + +```typescript +title: string; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.tospec.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.tospec.md new file mode 100644 index 0000000000000..5d76b8f00853b --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.tospec.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [toSpec](./kibana-plugin-plugins-data-server.indexpattern.tospec.md) + +## IndexPattern.toSpec() method + +Signature: + +```typescript +toSpec(): IndexPatternSpec; +``` +Returns: + +`IndexPatternSpec` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.type.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.type.md new file mode 100644 index 0000000000000..01154ab5444d1 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.type.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [type](./kibana-plugin-plugins-data-server.indexpattern.type.md) + +## IndexPattern.type property + +Signature: + +```typescript +type: string | undefined; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.typemeta.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.typemeta.md new file mode 100644 index 0000000000000..b16bcec404d97 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.typemeta.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [typeMeta](./kibana-plugin-plugins-data-server.indexpattern.typemeta.md) + +## IndexPattern.typeMeta property + +Signature: + +```typescript +typeMeta?: TypeMeta; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.version.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.version.md new file mode 100644 index 0000000000000..e4297d8389111 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.version.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [version](./kibana-plugin-plugins-data-server.indexpattern.version.md) + +## IndexPattern.version property + +Signature: + +```typescript +version: string | undefined; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.md index 4a5b61f5c179b..40b029da00469 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.md @@ -4,12 +4,6 @@ ## IndexPatternAttributes interface -> Warning: This API is now obsolete. -> -> - -Use data plugin interface instead - Signature: ```typescript diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.aggregatable.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.aggregatable.md deleted file mode 100644 index 92994b851ec85..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.aggregatable.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternFieldDescriptor](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.md) > [aggregatable](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.aggregatable.md) - -## IndexPatternFieldDescriptor.aggregatable property - -Signature: - -```typescript -aggregatable: boolean; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.estypes.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.estypes.md deleted file mode 100644 index f24ba9a48d85e..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.estypes.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternFieldDescriptor](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.md) > [esTypes](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.estypes.md) - -## IndexPatternFieldDescriptor.esTypes property - -Signature: - -```typescript -esTypes: string[]; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.md deleted file mode 100644 index d84d0cba06ac6..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternFieldDescriptor](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.md) - -## IndexPatternFieldDescriptor interface - -Signature: - -```typescript -export interface FieldDescriptor -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [aggregatable](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.aggregatable.md) | boolean | | -| [esTypes](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.estypes.md) | string[] | | -| [name](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.name.md) | string | | -| [readFromDocValues](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.readfromdocvalues.md) | boolean | | -| [searchable](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.searchable.md) | boolean | | -| [subType](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.subtype.md) | FieldSubType | | -| [type](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.type.md) | string | | - diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.name.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.name.md deleted file mode 100644 index 16ea60c5b8ae2..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.name.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternFieldDescriptor](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.md) > [name](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.name.md) - -## IndexPatternFieldDescriptor.name property - -Signature: - -```typescript -name: string; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.readfromdocvalues.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.readfromdocvalues.md deleted file mode 100644 index fc8667196c879..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.readfromdocvalues.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternFieldDescriptor](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.md) > [readFromDocValues](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.readfromdocvalues.md) - -## IndexPatternFieldDescriptor.readFromDocValues property - -Signature: - -```typescript -readFromDocValues: boolean; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.searchable.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.searchable.md deleted file mode 100644 index 7d159c65b40bd..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.searchable.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternFieldDescriptor](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.md) > [searchable](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.searchable.md) - -## IndexPatternFieldDescriptor.searchable property - -Signature: - -```typescript -searchable: boolean; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.subtype.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.subtype.md deleted file mode 100644 index 7053eaf08138c..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.subtype.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternFieldDescriptor](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.md) > [subType](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.subtype.md) - -## IndexPatternFieldDescriptor.subType property - -Signature: - -```typescript -subType?: FieldSubType; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.type.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.type.md deleted file mode 100644 index bb571d1bee14a..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternfielddescriptor.type.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternFieldDescriptor](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.md) > [type](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.type.md) - -## IndexPatternFieldDescriptor.type property - -Signature: - -```typescript -type: string; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.md new file mode 100644 index 0000000000000..aa78c055f4f5c --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) + +## IndexPatternsService class + +Signature: + +```typescript +export declare class IndexPatternsService implements Plugin +``` + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [setup(core)](./kibana-plugin-plugins-data-server.indexpatternsservice.setup.md) | | | +| [start(core, { fieldFormats, logger })](./kibana-plugin-plugins-data-server.indexpatternsservice.start.md) | | | + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.setup.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.setup.md new file mode 100644 index 0000000000000..a354fbc2a477b --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.setup.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) > [setup](./kibana-plugin-plugins-data-server.indexpatternsservice.setup.md) + +## IndexPatternsService.setup() method + +Signature: + +```typescript +setup(core: CoreSetup): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| core | CoreSetup | | + +Returns: + +`void` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.start.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.start.md new file mode 100644 index 0000000000000..d35dc3aa11000 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsservice.start.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) > [start](./kibana-plugin-plugins-data-server.indexpatternsservice.start.md) + +## IndexPatternsService.start() method + +Signature: + +```typescript +start(core: CoreStart, { fieldFormats, logger }: IndexPatternsServiceStartDeps): { + indexPatternsServiceFactory: (kibanaRequest: KibanaRequest) => Promise; + }; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| core | CoreStart | | +| { fieldFormats, logger } | IndexPatternsServiceStartDeps | | + +Returns: + +`{ + indexPatternsServiceFactory: (kibanaRequest: KibanaRequest) => Promise; + }` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md index 3c477e17503f4..7113ac935907f 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md @@ -9,7 +9,9 @@ | Class | Description | | --- | --- | | [AggParamType](./kibana-plugin-plugins-data-server.aggparamtype.md) | | +| [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) | | | [IndexPatternsFetcher](./kibana-plugin-plugins-data-server.indexpatternsfetcher.md) | | +| [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) | | | [OptionedParamType](./kibana-plugin-plugins-data-server.optionedparamtype.md) | | | [Plugin](./kibana-plugin-plugins-data-server.plugin.md) | | @@ -41,14 +43,13 @@ | --- | --- | | [AggParamOption](./kibana-plugin-plugins-data-server.aggparamoption.md) | | | [EsQueryConfig](./kibana-plugin-plugins-data-server.esqueryconfig.md) | | +| [FieldDescriptor](./kibana-plugin-plugins-data-server.fielddescriptor.md) | | | [FieldFormatConfig](./kibana-plugin-plugins-data-server.fieldformatconfig.md) | | | [IEsSearchRequest](./kibana-plugin-plugins-data-server.iessearchrequest.md) | | | [IEsSearchResponse](./kibana-plugin-plugins-data-server.iessearchresponse.md) | | | [IFieldSubType](./kibana-plugin-plugins-data-server.ifieldsubtype.md) | | | [IFieldType](./kibana-plugin-plugins-data-server.ifieldtype.md) | | -| [IIndexPattern](./kibana-plugin-plugins-data-server.iindexpattern.md) | | -| [IndexPatternAttributes](./kibana-plugin-plugins-data-server.indexpatternattributes.md) | Use data plugin interface instead | -| [IndexPatternFieldDescriptor](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.md) | | +| [IndexPatternAttributes](./kibana-plugin-plugins-data-server.indexpatternattributes.md) | | | [ISearchOptions](./kibana-plugin-plugins-data-server.isearchoptions.md) | | | [ISearchSetup](./kibana-plugin-plugins-data-server.isearchsetup.md) | | | [ISearchStart](./kibana-plugin-plugins-data-server.isearchstart.md) | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md index 455c5ecdd8195..84aeb4cf80cce 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md @@ -13,7 +13,7 @@ start(core: CoreStart): { fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (kibanaRequest: import("../../../core/server").KibanaRequest) => Promise; + indexPatternsServiceFactory: (kibanaRequest: import("../../../core/server").KibanaRequest) => Promise; }; }; ``` @@ -32,7 +32,7 @@ start(core: CoreStart): { fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (kibanaRequest: import("../../../core/server").KibanaRequest) => Promise; + indexPatternsServiceFactory: (kibanaRequest: import("../../../core/server").KibanaRequest) => Promise; }; }` diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index ed20166c87f29..1bae04cc2e58b 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -1,9 +1,10 @@ [[advanced-options]] == Advanced Settings -The *Advanced Settings* UI enables you to edit settings that control the behavior of Kibana. -For example, you can change the format used to display dates, specify the default index pattern, and set the precision -for displayed decimal values. +The *Advanced Settings* UI enables you to edit settings that control the +behavior of Kibana. For example, you can change the format used to display dates, +specify the default index pattern, and set the precision for displayed decimal +values. . Open the menu, then go to *Stack Management > {kib} > Advanced Settings*. . Scroll or search for the setting you want to modify. @@ -15,8 +16,9 @@ for displayed decimal values. [[settings-read-only-access]] === [xpack]#Read only access# When you have insufficient privileges to edit advanced settings, the following -indicator in Kibana will be displayed. The buttons to edit settings won't be visible. -For more information on granting access to Kibana see <>. +indicator in Kibana will be displayed. The buttons to edit settings won't be +visible. For more information on granting access to Kibana, see +<>. [role="screenshot"] image::images/settings-read-only-badge.png[Example of Advanced Settings Management's read only access indicator in Kibana's header] @@ -25,12 +27,11 @@ image::images/settings-read-only-badge.png[Example of Advanced Settings Manageme [[kibana-settings-reference]] === Kibana settings reference -WARNING: Modifying a setting can affect {kib} -performance and cause problems that are -difficult to diagnose. Setting a property value to a blank field reverts -to the default behavior, which might not be -compatible with other configuration settings. Deleting a custom setting -removes it from {kib} permanently. +WARNING: Modifying a setting can affect {kib} performance and cause problems +that are difficult to diagnose. Setting a property value to a blank field +reverts to the default behavior, which might not be compatible with other +configuration settings. Deleting a custom setting removes it from {kib} +permanently. [float] @@ -38,72 +39,159 @@ removes it from {kib} permanently. ==== General [horizontal] -`csv:quoteValues`:: Set this property to `true` to quote exported values. -`csv:separator`:: A string that serves as the separator for exported values. -`dateFormat`:: The format to use for displaying https://momentjs.com/docs/#/displaying/format/[pretty formatted dates]. -`dateFormat:dow`:: The day that a week should start on. -`dateFormat:scaled`:: The values that define the format to use to render ordered time-based data. Formatted timestamps must -adapt to the interval between measurements. Keys are http://en.wikipedia.org/wiki/ISO_8601#Time_intervals[ISO8601 intervals]. -`dateFormat:tz`:: The timezone that Kibana uses. The default value of `Browser` uses the timezone detected by the browser. -`dateNanosFormat`:: The format to use for displaying https://momentjs.com/docs/#/displaying/format/[pretty formatted dates] of {ref}/date_nanos.html[Elasticsearch date_nanos type]. -`defaultIndex`:: The index to access if no index is set. The default is `null`. -`defaultRoute`:: The default route when opening Kibana. Use this setting to route users to a specific dashboard, application, or saved object as they enter each space. -`fields:popularLimit`:: The top N most popular fields to show. -`filterEditor:suggestValues`:: Set this property to `false` to prevent the filter editor from suggesting values for fields. -`filters:pinnedByDefault`:: Set this property to `true` to make filters have a global state (be pinned) by default. -`format:bytes:defaultPattern`:: The default <> format for the "bytes" format. -`format:currency:defaultPattern`:: The default <> format for the "currency" format. -`format:defaultTypeMap`:: A map of the default format name for each field type. Field types that are not explicitly -mentioned use "\_default_". -`format:number:defaultLocale`:: The <> locale. -`format:number:defaultPattern`:: The <> for the "number" format. -`format:percent:defaultPattern`:: The <> for the "percent" format. -`histogram:barTarget`:: When date histograms use the `auto` interval, Kibana attempts to generate this number of bars. -`histogram:maxBars`:: Date histograms are not generated with more bars than the value of this property, scaling values -when necessary. -`history:limit`:: In fields that have history, such as query inputs, show this many recent values. -`indexPattern:placeholder`:: The default placeholder value to use in Management > Index Patterns > Create Index Pattern. -`metaFields`:: Fields that exist outside of `_source`. Kibana merges these fields -into the document when displaying it. -`metrics:max_buckets`:: The maximum numbers of buckets that a single -data source can return. This might arise when the user selects a -short interval (for example, 1s) for a long time period (1 year). -`pageNavigation`:: The style of navigation menu for Kibana. -Choices are Legacy, the legacy style where every plugin is represented in the nav, -and Modern, a new format that bundles related plugins together in flyaway nested navigation. -`query:allowLeadingWildcards`:: Allows a wildcard (*) as the first character -in a query clause. Only applies when experimental query features are -enabled in the query bar. To disallow leading wildcards in Lucene queries, -use `query:queryString:options`. -`query:queryString:options`:: Options for the Lucene query string parser. Only -used when "Query language" is set to Lucene. -`savedObjects:listingLimit`:: The number of objects to fetch for lists of saved objects. -The default value is 1000. Do not set above 10000. -`savedObjects:perPage`:: The number of objects to show on each page of the -list of saved objects. The default is 5. -`search:queryLanguage`:: The query language to use in the query bar. -Choices are <>, a language built specifically for {kib}, and the <>. -`shortDots:enable`:: Set this property to `true` to shorten long -field names in visualizations. For example, show `f.b.baz` instead of `foo.bar.baz`. -`sort:options`:: Options for the Elasticsearch {ref}/search-request-body.html#request-body-search-sort[sort] parameter. -`state:storeInSessionStorage`:: [experimental] Kibana tracks UI state in the -URL, which can lead to problems when there is a lot of state information, -and the URL gets very long. -Enabling this setting stores part of the URL in your browser session to keep the -URL short. -`theme:darkMode`:: Set to `true` to enable a dark mode for the {kib} UI. You must -refresh the page to apply the setting. -`timepicker:quickRanges`:: The list of ranges to show in the Quick section of -the time filter. This should be an array of objects, with each object containing -`from`, `to` (see {ref}/common-options.html#date-math[accepted formats]), -and `display` (the title to be displayed). -`timepicker:refreshIntervalDefaults`:: The default refresh interval for the time filter. Example: `{ "display": "15 seconds", "pause": true, "value": 15000 }`. -`timepicker:timeDefaults`:: The default selection in the time filter. -`truncate:maxHeight`:: The maximum height that a cell occupies in a table. Set to 0 to disable +[[csv-quotevalues]]`csv:quoteValues`:: +Set this property to `true` to quote exported values. + +[[csv-separator]]`csv:separator`:: +A string that serves as the separator for exported values. + +[[dateformat]]`dateFormat`:: +The format to use for displaying +https://momentjs.com/docs/#/displaying/format/[pretty formatted dates]. + +[[dateformat-dow]]`dateFormat:dow`:: +The day that a week should start on. + +[[dateformat-scaled]]`dateFormat:scaled`:: +The values that define the format to use to render ordered time-based data. +Formatted timestamps must adapt to the interval between measurements. Keys are +http://en.wikipedia.org/wiki/ISO_8601#Time_intervals[ISO8601 intervals]. + +[[dateformat-tz]]`dateFormat:tz`:: +The timezone that Kibana uses. The default value of `Browser` uses the timezone +detected by the browser. + +[[datenanosformat]]`dateNanosFormat`:: +The format to use for displaying +https://momentjs.com/docs/#/displaying/format/[pretty formatted dates] of +{ref}/date_nanos.html[Elasticsearch date_nanos type]. + +[[defaultindex]]`defaultIndex`:: +The index to access if no index is set. The default is `null`. + +[[defaultroute]]`defaultRoute`:: +The default route when opening Kibana. Use this setting to route users to a +specific dashboard, application, or saved object as they enter each space. + +[[fields-popularlimit]]`fields:popularLimit`:: +The top N most popular fields to show. + +[[filtereditor-suggestvalues]]`filterEditor:suggestValues`:: +Set this property to `false` to prevent the filter editor from suggesting values +for fields. + +[[filters-pinnedbydefault]]`filters:pinnedByDefault`:: +Set this property to `true` to make filters have a global state (be pinned) by +default. + +[[format-bytes-defaultpattern]]`format:bytes:defaultPattern`:: +The default <> format for the "bytes" format. + +[[format-currency-defaultpattern]]`format:currency:defaultPattern`:: +The default <> format for the "currency" format. + +[[format-defaulttypemap]]`format:defaultTypeMap`:: +A map of the default format name for each field type. Field types that are not +explicitly mentioned use "\_default_". + +[[format-number-defaultlocale]]`format:number:defaultLocale`:: +The <> locale. + +[[format-number-defaultpattern]]`format:number:defaultPattern`:: +The <> for the "number" format. + +[[format-percent-defaultpattern]]`format:percent:defaultPattern`:: +The <> for the "percent" format. + +[[histogram-bartarget]]`histogram:barTarget`:: +When date histograms use the `auto` interval, Kibana attempts to generate this +number of bars. + +[[histogram-maxbars]]`histogram:maxBars`:: +Date histograms are not generated with more bars than the value of this property, +scaling values when necessary. + +[[history-limit]]`history:limit`:: +In fields that have history, such as query inputs, show this many recent values. + +[[indexpattern-placeholder]]`indexPattern:placeholder`:: +The default placeholder value to use in +*Management > Index Patterns > Create Index Pattern*. + +[[metafields]]`metaFields`:: +Fields that exist outside of `_source`. Kibana merges these fields into the +document when displaying it. + +[[metrics-maxbuckets]]`metrics:max_buckets`:: +The maximum numbers of buckets that a single data source can return. This might +arise when the user selects a short interval (for example, 1s) for a long time +period (1 year). + +[[pagenavigation]]`pageNavigation`:: +The style of navigation menu for Kibana. Choices are Legacy, the legacy style +where every plugin is represented in the nav, and Modern, a new format that +bundles related plugins together in flyaway nested navigation. + +[[query-allowleadingwildcards]]`query:allowLeadingWildcards`:: +Allows a wildcard (*) as the first character in a query clause. Only applies +when experimental query features are enabled in the query bar. To disallow +leading wildcards in Lucene queries, use `query:queryString:options`. + +[[query-querystring-options]]`query:queryString:options`:: +Options for the Lucene query string parser. Only used when "Query language" is +set to Lucene. + +[[savedobjects-listinglimit]]`savedObjects:listingLimit`:: +The number of objects to fetch for lists of saved objects. The default value +is 1000. Do not set above 10000. + +[[savedobjects-perpage]]`savedObjects:perPage`:: +The number of objects to show on each page of the list of saved objects. The +default is 5. + +[[search-querylanguage]]`search:queryLanguage`:: +The query language to use in the query bar. Choices are <>, a +language built specifically for {kib}, and the +<>. + +[[shortdots-enable]]`shortDots:enable`:: +Set this property to `true` to shorten long field names in visualizations. For +example, show `f.b.baz` instead of `foo.bar.baz`. + +[[sort-options]]`sort:options`:: Options for the Elasticsearch +{ref}/search-request-body.html#request-body-search-sort[sort] parameter. + +[[state-storeinsessionstorage]]`state:storeInSessionStorage`:: +experimental:[] +Kibana tracks UI state in the URL, which can lead to problems +when there is a lot of state information, and the URL gets very long. Enabling +this setting stores part of the URL in your browser session to keep the URL +short. + +[[theme-darkmode]]`theme:darkMode`:: +Set to `true` to enable a dark mode for the {kib} UI. You must refresh the page +to apply the setting. + +[[timepicker-quickranges]]`timepicker:quickRanges`:: +The list of ranges to show in the Quick section of the time filter. This should +be an array of objects, with each object containing `from`, `to` (see +{ref}/common-options.html#date-math[accepted formats]), and `display` (the title +to be displayed). + +[[timepicker-refreshintervaldefaults]]`timepicker:refreshIntervalDefaults`:: +The default refresh interval for the time filter. Example: +`{ "display": "15 seconds", "pause": true, "value": 15000 }`. + +[[timepicker-timedefaults]]`timepicker:timeDefaults`:: +The default selection in the time filter. + +[[truncate-maxheight]]`truncate:maxHeight`:: +The maximum height that a cell occupies in a table. Set to 0 to disable truncation. -`xPack:defaultAdminEmail`:: Email address for X-Pack admin operations, such as -cluster alert notifications from Monitoring. + +[[xpack-defaultadminemail]]`xPack:defaultAdminEmail`:: +Email address for {xpack} admin operations, such as cluster alert notifications +from *{stack-monitor-app}*. [float] @@ -111,15 +199,17 @@ cluster alert notifications from Monitoring. ==== Accessibility [horizontal] -`accessibility:disableAnimations`:: Turns off all unnecessary animations in the -{kib} UI. Refresh the page to apply the changes. +[[accessibility-disableanimations]]`accessibility:disableAnimations`:: +Turns off all unnecessary animations in the {kib} UI. Refresh the page to apply +the changes. [float] [[kibana-dashboard-settings]] ==== Dashboard [horizontal] -`xpackDashboardMode:roles`:: **Deprecated. Use <> instead.** +[[xpackdashboardmode-roles]]`xpackDashboardMode:roles`:: +**Deprecated. Use <> instead.** The roles that belong to <>. [float] @@ -127,38 +217,63 @@ The roles that belong to <>. ==== Discover [horizontal] -`context:defaultSize`:: The number of surrounding entries to display in the context view. The default value is 5. -`context:step`:: The number by which to increment or decrement the context size. The default value is 5. -`context:tieBreakerFields`:: A comma-separated list of fields to use -for breaking a tie between documents that have the same timestamp value. The first -field that is present and sortable in the current index pattern is used. -`defaultColumns`:: The columns that appear by default on the Discover page. -The default is `_source`. -`discover:aggs:terms:size`:: The number terms that are visualized when clicking -the Visualize button in the field drop down. The default is `20`. -`discover:sampleSize`:: The number of rows to show in the Discover table. -`discover:sort:defaultOrder`:: The default sort direction for time-based index patterns. -`discover:searchOnPageLoad`:: Controls whether a search is executed when Discover first loads. -This setting does not have an effect when loading a saved search. -`doc_table:hideTimeColumn`:: Hides the "Time" column in Discover and in all saved searches on dashboards. -`doc_table:highlight`:: Highlights results in Discover and saved searches on dashboards. -Highlighting slows requests when -working on big documents. +[[context-defaultsize]]`context:defaultSize`:: +The number of surrounding entries to display in the context view. The default +value is 5. + +[[context-step]]`context:step`:: +The number by which to increment or decrement the context size. The default +value is 5. + +[[context-tiebreakerfields]]`context:tieBreakerFields`:: +A comma-separated list of fields to use for breaking a tie between documents +that have the same timestamp value. The first field that is present and sortable +in the current index pattern is used. + +[[defaultcolumns]]`defaultColumns`:: +The columns that appear by default on the *Discover* page. The default is +`_source`. + +[[discover-aggs-terms-size]]`discover:aggs:terms:size`:: +The number terms that are visualized when clicking the *Visualize* button in the +field drop down. The default is `20`. + +[[discover-samplesize]]`discover:sampleSize`:: +The number of rows to show in the *Discover* table. + +[[discover-sort-defaultorder]]`discover:sort:defaultOrder`:: +The default sort direction for time-based index patterns. + +[[discover-searchonpageload]]`discover:searchOnPageLoad`:: +Controls whether a search is executed when *Discover* first loads. This setting +does not have an effect when loading a saved search. + +[[doctable-hidetimecolumn]]`doc_table:hideTimeColumn`:: +Hides the "Time" column in *Discover* and in all saved searches on dashboards. + +[[doctable-highlight]]`doc_table:highlight`:: +Highlights results in *Discover* and saved searches on dashboards. Highlighting +slows requests when working on big documents. [float] [[kibana-ml-settings]] ==== Machine learning [horizontal] -`ml:anomalyDetection:results:enableTimeDefaults`:: Use the default time filter -in the *Single Metric Viewer* and *Anomaly Explorer*. If this setting is -disabled, the results for the full time range are shown. -`ml:anomalyDetection:results:timeDefaults`:: Sets the default time filter for -viewing {anomaly-job} results. This setting must contain `from` and `to` values (see {ref}/common-options.html#date-math[accepted formats]). It is ignored -unless `ml:anomalyDetection:results:enableTimeDefaults` is enabled. -`ml:fileDataVisualizerMaxFileSize`:: Sets the file size limit when importing -data in the {data-viz}. The default value is `100MB`. The highest supported -value for this setting is `1GB`. +[[ml-anomalydetection-results-enabletimedefaults]]`ml:anomalyDetection:results:enableTimeDefaults`:: +Use the default time filter in the *Single Metric Viewer* and +*Anomaly Explorer*. If this setting is disabled, the results for the full time +range are shown. + +[[ml-anomalydetection-results-timedefaults]]`ml:anomalyDetection:results:timeDefaults`:: +Sets the default time filter for viewing {anomaly-job} results. This setting +must contain `from` and `to` values (see +{ref}/common-options.html#date-math[accepted formats]). It is ignored unless +`ml:anomalyDetection:results:enableTimeDefaults` is enabled. + +[[ml-filedatavisualizermaxfilesize]]`ml:fileDataVisualizerMaxFileSize`:: +Sets the file size limit when importing data in the {data-viz}. The default +value is `100MB`. The highest supported value for this setting is `1GB`. [float] @@ -166,18 +281,26 @@ value for this setting is `1GB`. ==== Notifications [horizontal] -`notifications:banner`:: A custom banner intended for temporary notices to all users. -Supports https://help.github.com/en/articles/basic-writing-and-formatting-syntax[Markdown]. -`notifications:lifetime:banner`:: The duration, in milliseconds, for banner -notification displays. The default value is 3000000. Set this field to `Infinity` -to disable banner notifications. -`notifications:lifetime:error`:: The duration, in milliseconds, for error -notification displays. The default value is 300000. Set this field to `Infinity` to disable error notifications. -`notifications:lifetime:info`:: The duration, in milliseconds, for information notification displays. -The default value is 5000. Set this field to `Infinity` to disable information notifications. -`notifications:lifetime:warning`:: The duration, in milliseconds, for warning notification -displays. The default value is 10000. Set this field to `Infinity` to disable warning notifications. +[[notifications-banner]]`notifications:banner`:: +A custom banner intended for temporary notices to all users. Supports +https://help.github.com/en/articles/basic-writing-and-formatting-syntax[Markdown]. +[[notifications-lifetime-banner]]`notifications:lifetime:banner`:: +The duration, in milliseconds, for banner notification displays. The default +value is 3000000. Set this field to `Infinity` to disable banner notifications. + +[[notificatios-lifetime-error]]`notifications:lifetime:error`:: +The duration, in milliseconds, for error notification displays. The default +value is 300000. Set this field to `Infinity` to disable error notifications. + +[[notifications-lifetime-info]]`notifications:lifetime:info`:: +The duration, in milliseconds, for information notification displays. The +default value is 5000. Set this field to `Infinity` to disable information +notifications. + +[[notifications-lifetime-warning]]`notifications:lifetime:warning`:: +The duration, in milliseconds, for warning notification displays. The default +value is 10000. Set this field to `Infinity` to disable warning notifications. [float] @@ -185,7 +308,8 @@ displays. The default value is 10000. Set this field to `Infinity` to disable wa ==== Reporting [horizontal] -`xpackReporting:customPdfLogo`:: A custom image to use in the footer of the PDF. +[[xpackreporting-custompdflogo]]`xpackReporting:customPdfLogo`:: +A custom image to use in the footer of the PDF. [float] @@ -193,9 +317,10 @@ displays. The default value is 10000. Set this field to `Infinity` to disable wa ==== Rollup [horizontal] -`rollups:enableIndexPatterns`:: Enables the creation of index patterns that -capture rollup indices, which in turn enables visualizations based on rollup data. -Refresh the page to apply the changes. +[[rollups-enableindexpatterns]]`rollups:enableIndexPatterns`:: +Enables the creation of index patterns that capture rollup indices, which in +turn enables visualizations based on rollup data. Refresh the page to apply the +changes. [float] @@ -203,67 +328,117 @@ Refresh the page to apply the changes. ==== Search [horizontal] -`courier:batchSearches`:: **Deprecated in 7.6. Starting in 8.0, this setting will be optimized internally.** -When disabled, dashboard panels will load individually, and search requests will terminate when -users navigate away or update the query. When enabled, dashboard panels will load together when all of the data is loaded, -and searches will not terminate. -`courier:customRequestPreference`:: {ref}/search-request-body.html#request-body-search-preference[Request preference] +[[courier-batchsearches]]`courier:batchSearches`:: +**Deprecated in 7.6. Starting in 8.0, this setting will be optimized internally.** +When disabled, dashboard panels will load individually, and search requests will +terminate when users navigate away or update the query. When enabled, dashboard +panels will load together when all of the data is loaded, and searches will not +terminate. + +[[courier-customrequestpreference]]`courier:customRequestPreference`:: +{ref}/search-request-body.html#request-body-search-preference[Request preference] to use when `courier:setRequestPreference` is set to "custom". -`courier:ignoreFilterIfFieldNotInIndex`:: Skips filters that apply to fields that don't exist in the index for a visualization. -Useful when dashboards consist of visualizations from multiple index patterns. -`courier:maxConcurrentShardRequests`:: Controls the {ref}/search-multi-search.html[max_concurrent_shard_requests] + +[[courier-ignorefilteriffieldnotinindex]]`courier:ignoreFilterIfFieldNotInIndex`:: +Skips filters that apply to fields that don't exist in the index for a +visualization. Useful when dashboards consist of visualizations from multiple +index patterns. + +[[courier-maxconcurrentshardrequests]]`courier:maxConcurrentShardRequests`:: +Controls the {ref}/search-multi-search.html[max_concurrent_shard_requests] setting used for `_msearch` requests sent by {kib}. Set to 0 to disable this config and use the {es} default. -`courier:setRequestPreference`:: Enables you to set which shards handle your search requests. -* *Session ID:* Restricts operations to execute all search requests on the same shards. -This has the benefit of reusing shard caches across requests. -* *Custom:* Allows you to define your own preference. Use `courier:customRequestPreference` -to customize your preference value. + +[[courier-setrequestpreference]]`courier:setRequestPreference`:: +Enables you to set which shards handle your search requests. +* *Session ID:* Restricts operations to execute all search requests on the same +shards. This has the benefit of reusing shard caches across requests. +* *Custom:* Allows you to define your own preference. Use +`courier:customRequestPreference` to customize your preference value. * *None:* Do not set a preference. This might provide better performance because requests can be spread across all shard copies. However, results might be inconsistent because different shards might be in different refresh states. -`search:includeFrozen`:: Includes {ref}/frozen-indices.html[frozen indices] in results. -Searching through frozen indices -might increase the search time. This setting is off by default. Users must opt-in to include frozen indices. -`search:timeout`:: Change the maximum timeout for a search session or set to 0 to disable the timeout and allow queries to run to completion. + +[[search-includefrozen]]`search:includeFrozen`:: +Includes {ref}/frozen-indices.html[frozen indices] in results. Searching through +frozen indices might increase the search time. This setting is off by default. +Users must opt-in to include frozen indices. + +[[search-timeout]]`search:timeout`:: Change the maximum timeout for a search +session or set to 0 to disable the timeout and allow queries to run to +completion. [float] [[kibana-siem-settings]] -==== Security Solution +==== Security solution [horizontal] -`securitySolution:defaultAnomalyScore`:: The threshold above which Machine Learning job anomalies are displayed in the Security app. -`securitySolution:defaultIndex`:: A comma-delimited list of Elasticsearch indices from which the Security app collects events. -`securitySolution:ipReputationLinks`:: A JSON array containing links for verifying the reputation of an IP address. The links are displayed on -{security-guide}/network-page-overview.html[IP detail] pages. -`securitySolution:enableNewsFeed`:: Enables the security news feed on the Security *Overview* -page. -`securitySolution:newsFeedUrl`:: The URL from which the security news feed content is -retrieved. -`securitySolution:refreshIntervalDefaults`:: The default refresh interval for the Security time filter, in milliseconds. -`securitySolution:timeDefaults`:: The default period of time in the Security time filter. +[[securitysolution-defaultanomalyscore]]`securitySolution:defaultAnomalyScore`:: +The threshold above which {ml} job anomalies are displayed in the {security-app}. + +[[securitysolution-defaultindex]]`securitySolution:defaultIndex`:: +A comma-delimited list of {es} indices from which the {security-app} collects +events. + +[[securitysolution-ipreputationlinks]]`securitySolution:ipReputationLinks`:: +A JSON array containing links for verifying the reputation of an IP address. The +links are displayed on {security-guide}/network-page-overview.html[IP detail] +pages. + +[[securitysolution-enablenewsfeed]]`securitySolution:enableNewsFeed`:: Enables +the security news feed on the Security *Overview* page. + +[[securitysolution-newsfeedurl]]`securitySolution:newsFeedUrl`:: +The URL from which the security news feed content is retrieved. + +[[securitysolution-refreshintervaldefaults]]`securitySolution:refreshIntervalDefaults`:: +The default refresh interval for the Security time filter, in milliseconds. + +[[securitysolution-timedefaults]]`securitySolution:timeDefaults`:: +The default period of time in the Security time filter. [float] [[kibana-timelion-settings]] ==== Timelion [horizontal] -`timelion:default_columns`:: The default number of columns to use on a Timelion sheet. -`timelion:default_rows`:: The default number of rows to use on a Timelion sheet. -`timelion:es.default_index`:: The default index when using the `.es()` query. -`timelion:es.timefield`:: The default field containing a timestamp when using the `.es()` query. -`timelion:graphite.url`:: [experimental] Used with graphite queries, this is the URL of your graphite host -in the form https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite. This URL can be -selected from an allow-list configured in the `kibana.yml` under `timelion.graphiteUrls`. -`timelion:max_buckets`:: The maximum number of buckets a single data source can return. -This value is used for calculating automatic intervals in visualizations. -`timelion:min_interval`:: The smallest interval to calculate when using "auto". -`timelion:quandl.key`:: [experimental] Used with quandl queries, this is your API key from https://www.quandl.com/[www.quandl.com]. -`timelion:showTutorial`:: Shows the Timelion tutorial -to users when they first open the Timelion app. -`timelion:target_buckets`:: Used for calculating automatic intervals in visualizations, -this is the number of buckets to try to represent. +[[timelion-defaultcolumns]]`timelion:default_columns`:: +The default number of columns to use on a Timelion sheet. + +[[timelion-defaultrows]]`timelion:default_rows`:: +The default number of rows to use on a Timelion sheet. + +[[timelion-esdefaultindex]]`timelion:es.default_index`:: +The default index when using the `.es()` query. + +[[timelion-estimefield]]`timelion:es.timefield`:: +The default field containing a timestamp when using the `.es()` query. + +[[timelion-graphite-url]]`timelion:graphite.url`:: +experimental:[] +Used with graphite queries, this is the URL of your graphite host +in the form https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite. This URL can +be selected from an allow-list configured in the `kibana.yml` under +`timelion.graphiteUrls`. + +[[timelion-maxbuckets]]`timelion:max_buckets`:: +The maximum number of buckets a single data source can return. This value is +used for calculating automatic intervals in visualizations. +[[timelion-mininterval]]`timelion:min_interval`:: +The smallest interval to calculate when using "auto". + +[[timelion-quandlkey]]`timelion:quandl.key`:: +experimental:[] +Used with quandl queries, this is your API key from +https://www.quandl.com/[www.quandl.com]. + +[[timelion-showtutorial]]`timelion:showTutorial`:: +Shows the Timelion tutorial to users when they first open the Timelion app. + +[[timelion-targetbuckets]]`timelion:target_buckets`:: +Used for calculating automatic intervals in visualizations, this is the number +of buckets to try to represent. [float] @@ -271,20 +446,32 @@ this is the number of buckets to try to represent. ==== Visualization [horizontal] -`visualization:colorMapping`:: Maps values to specified colors in visualizations. -`visualization:dimmingOpacity`:: The opacity of the chart items that are dimmed -when highlighting another element of the chart. The lower this number, the more -the highlighted element stands out. This must be a number between 0 and 1. -`visualization:loadingDelay`:: The time to wait before dimming visualizations -during a query. -`visualization:regionmap:showWarnings`:: Shows -a warning in a region map when terms cannot be joined to a shape. -`visualization:tileMap:WMSdefaults`:: The default properties for the WMS map server support in the coordinate map. -`visualization:tileMap:maxPrecision`:: The maximum geoHash precision displayed on tile maps: 7 is high, 10 is very high, +[[visualization-colormapping]]`visualization:colorMapping`:: +Maps values to specified colors in visualizations. + +[[visualization-dimmingopacity]]`visualization:dimmingOpacity`:: +The opacity of the chart items that are dimmed when highlighting another element +of the chart. The lower this number, the more the highlighted element stands out. +This must be a number between 0 and 1. + +[[visualization-loadingdelay]]`visualization:loadingDelay`:: +The time to wait before dimming visualizations during a query. + +[[visualization-regionmap-showwarnings]]`visualization:regionmap:showWarnings`:: +Shows a warning in a region map when terms cannot be joined to a shape. + +[[visualization-tilemap-wmsdefaults]]`visualization:tileMap:WMSdefaults`:: +The default properties for the WMS map server support in the coordinate map. + +[[visualization-tilemap-maxprecision]]`visualization:tileMap:maxPrecision`:: +The maximum geoHash precision displayed on tile maps: 7 is high, 10 is very high, and 12 is the maximum. See this {ref}/search-aggregations-bucket-geohashgrid-aggregation.html#_cell_dimensions_at_the_equator[explanation of cell dimensions]. -`visualize:enableLabs`:: Enables users to create, view, and edit experimental visualizations. -If disabled, only visualizations that are considered production-ready are available to the user. + +[[visualize-enablelabs]]`visualize:enableLabs`:: +Enables users to create, view, and edit experimental visualizations. If disabled, +only visualizations that are considered production-ready are available to the +user. [float] diff --git a/docs/management/images/add_remote_cluster.png b/docs/management/images/add_remote_cluster.png deleted file mode 100755 index 160d29b741c62..0000000000000 Binary files a/docs/management/images/add_remote_cluster.png and /dev/null differ diff --git a/docs/management/images/auto_follow_pattern.png b/docs/management/images/auto_follow_pattern.png deleted file mode 100755 index f80de9352280f..0000000000000 Binary files a/docs/management/images/auto_follow_pattern.png and /dev/null differ diff --git a/docs/management/images/cross-cluster-replication-list-view.png b/docs/management/images/cross-cluster-replication-list-view.png deleted file mode 100755 index 4c45174cff7f1..0000000000000 Binary files a/docs/management/images/cross-cluster-replication-list-view.png and /dev/null differ diff --git a/docs/management/images/remote-clusters-list-view.png b/docs/management/images/remote-clusters-list-view.png deleted file mode 100755 index c28379863b74b..0000000000000 Binary files a/docs/management/images/remote-clusters-list-view.png and /dev/null differ diff --git a/docs/management/managing-ccr.asciidoc b/docs/management/managing-ccr.asciidoc deleted file mode 100644 index 9c06e479e28b2..0000000000000 --- a/docs/management/managing-ccr.asciidoc +++ /dev/null @@ -1,80 +0,0 @@ -[role="xpack"] -[[managing-cross-cluster-replication]] -== Cross-Cluster Replication - -Use *Cross-Cluster Replication* to reproduce indices in -remote clusters on a local cluster. {ref}/xpack-ccr.html[Cross-cluster replication] -is commonly used to provide remote backups for disaster recovery and for -geo-proximite copies of data. - -To get started, open the menu, then go to *Stack Management > Data > Cross-Cluster Replication*. - -[role="screenshot"] -image::images/cross-cluster-replication-list-view.png[][Cross-cluster replication list view] - -[float] -=== Prerequisites - -* You must have a {ref}/modules-remote-clusters.html[remote cluster]. -* Leader indices must meet {ref}/ccr-requirements.html[these requirements]. -* The Elasticsearch version of the local cluster must be the same as or newer than the remote cluster. -Refer to {ref}/ccr-overview.html[this document] for more information. - -[float] -=== Required permissions - -The `manage` and `manage_ccr` cluster privileges are required to access *Cross-Cluster Replication*. - -You can add these privileges in *Stack Management > Security > Roles*. - -[float] -[[configure-replication]] -=== Configure replication - -Replication requires a leader index, the index being replicated, and a -follower index, which will contain the leader index's replicated data. -The follower index is passive in that it can read requests and searches, -but cannot accept direct writes. Only the leader index is active for direct writes. - -You can configure follower indices in two ways: - -* Create specific follower indices -* Create follower indices from an auto-follow pattern - -[float] -==== Create specific follower indices - -To replicate data from existing indices, or set up local followers on a case-by-case basis, -go to *Follower indices*. When you create the follower index, you must reference the -remote cluster and the leader index that you created in the remote cluster. - -[role="screenshot"] -image::images/follower_indices.png[][UI for adding follower indices] - -[float] -==== Create follower indices from an auto-follow pattern - -To automatically detect and follow new indices when they are created on a remote cluster, -go to *Auto-follow patterns*. Creating an auto-follow pattern is useful when you have -time series data, like event logs, on the remote cluster that is created or rolled over on a daily basis. - -When creating the pattern, you must reference the remote cluster that you -connected to your local cluster. You must also specify a collection of index patterns -that match the indices you want to automatically follow. - -Once you configure an -auto-follow pattern, any time a new index with a name that matches the pattern is -created in the remote cluster, a follower index is automatically configured in the local cluster. - -[role="screenshot"] -image::images/auto_follow_pattern.png[UI for adding an auto-follow pattern] - -[float] -[[manage-replication]] -=== Manage replication - -Use the list views in *Cross-Cluster Replication* to monitor whether the replication is active and -pause and resume replication. You can also edit and remove the follower indices and auto-follow patterns. - -For an example of cross-cluster replication, -refer to https://www.elastic.co/blog/bi-directional-replication-with-elasticsearch-cross-cluster-replication-ccr[Bi-directional replication with Elasticsearch cross-cluster replication]. diff --git a/docs/management/managing-remote-clusters.asciidoc b/docs/management/managing-remote-clusters.asciidoc deleted file mode 100644 index 92e0fa822b056..0000000000000 --- a/docs/management/managing-remote-clusters.asciidoc +++ /dev/null @@ -1,50 +0,0 @@ -[[working-remote-clusters]] -== Remote Clusters - -Use *Remote Clusters* to establish a unidirectional -connection from your cluster to other clusters. This functionality is -required for {ref}/xpack-ccr.html[cross-cluster replication] and -{ref}/modules-cross-cluster-search.html[cross-cluster search]. - -To get started, open the menu, then go to *Stack Management > Data > Remote Clusters*. - -[role="screenshot"] -image::images/remote-clusters-list-view.png[Remote Clusters list view, including Add a remote cluster button] - -[float] -=== Required permissions - -The `manage` cluster privilege is required to access *Remote Clusters*. - -You can add this privilege in *Stack Management > Security > Roles*. - -[float] -[[managing-remote-clusters]] -=== Add a remote cluster - -A {ref}/modules-remote-clusters.html[remote cluster] connection works by configuring a remote cluster and -connecting to a limited number of nodes, called {ref}/modules-remote-clusters.html#sniff-mode[seed nodes], -in that cluster. -Alternatively, you can define a single proxy address for the remote cluster. - -By default, a cross-cluster request, such as a cross-cluster search or -replication request, fails if any cluster in the request is unavailable. -To skip a cluster when its unavailable, -set *Skip if unavailable* to true. - -Once you add a remote cluster, you can configure <> -to reproduce indices in the remote cluster on a local cluster. - -[role="screenshot"] -image::images/add_remote_cluster.png[][UI for adding a remote cluster] - -To create an index pattern to search across clusters, -use the same syntax that you’d use in a raw cross-cluster search request in {es}: :. -See <> for examples. - -[float] -[[manage-remote-clusters]] -=== Manage remote clusters - -From the *Remote Clusters* list view, you can drill down into each cluster and -view its status. You can also edit and delete a cluster. diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index 42d1d89145d79..5067bc08bec99 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -100,3 +100,15 @@ This content has moved to the <> page. == TSVB This page was deleted. See <>. + +[role="exclude",id="managing-cross-cluster-replication"] +== Cross-Cluster Replication + +This content has moved. See +{ref}/ccr-getting-started.html[Set up cross-cluster replication]. + +[role="exclude",id="working-remote-clusters"] +== Remote clusters + +This content has moved. See +{ref}/ccr-getting-started.html#ccr-getting-started-remote-cluster[Connect to a remote cluster]. diff --git a/docs/settings/monitoring-settings.asciidoc b/docs/settings/monitoring-settings.asciidoc index 6c8632efa9cc0..917821ad09e2f 100644 --- a/docs/settings/monitoring-settings.asciidoc +++ b/docs/settings/monitoring-settings.asciidoc @@ -33,7 +33,7 @@ For more information, see |=== | `monitoring.enabled` | Set to `true` (default) to enable the {monitor-features} in {kib}. Unlike the - `monitoring.ui.enabled` setting, when this setting is `false`, the + <> setting, when this setting is `false`, the monitoring back-end does not run and {kib} stats are not sent to the monitoring cluster. @@ -44,7 +44,7 @@ a|`monitoring.cluster_alerts.` | `monitoring.ui.elasticsearch.hosts` | Specifies the location of the {es} cluster where your monitoring data is stored. - By default, this is the same as `elasticsearch.hosts`. This setting enables + By default, this is the same as <>. This setting enables you to use a single {kib} instance to search and visualize data in your production cluster as well as monitor data sent to a dedicated monitoring cluster. @@ -58,7 +58,7 @@ a|`monitoring.cluster_alerts.` cluster uses the authenticated user's credentials, which must be the same on both the {es} monitoring cluster and the {es} production cluster. + + - If not set, {kib} uses the value of the `elasticsearch.username` setting. + If not set, {kib} uses the value of the <> setting. | `monitoring.ui.elasticsearch.password` | Specifies the password used by {kib} monitoring to establish a persistent connection @@ -69,11 +69,11 @@ a|`monitoring.cluster_alerts.` cluster uses the authenticated user's credentials, which must be the same on both the {es} monitoring cluster and the {es} production cluster. + + - If not set, {kib} uses the value of the `elasticsearch.password` setting. + If not set, {kib} uses the value of the <> setting. | `monitoring.ui.elasticsearch.pingTimeout` | Specifies the time in milliseconds to wait for {es} to respond to internal - health checks. By default, it matches the `elasticsearch.pingTimeout` setting, + health checks. By default, it matches the <> setting, which has a default value of `30000`. |=== @@ -112,7 +112,7 @@ about configuring {kib}, see | Specifies the number of log entries to display in *{stack-monitor-app}*. Defaults to `10`. The maximum value is `50`. -| `monitoring.ui.enabled` +|[[monitoring-ui-enabled]] `monitoring.ui.enabled` | Set to `false` to hide *{stack-monitor-app}*. The monitoring back-end continues to run as an agent for sending {kib} stats to the monitoring cluster. Defaults to `true`. diff --git a/docs/settings/reporting-settings.asciidoc b/docs/settings/reporting-settings.asciidoc index 3489dcd018293..adfc3964d4204 100644 --- a/docs/settings/reporting-settings.asciidoc +++ b/docs/settings/reporting-settings.asciidoc @@ -20,7 +20,7 @@ You can configure `xpack.reporting` settings in your `kibana.yml` to: | [[xpack-enable-reporting]]`xpack.reporting.enabled` {ess-icon} | Set to `false` to disable the {report-features}. -| `xpack.reporting.encryptionKey` {ess-icon} +|[[xpack-reporting-encryptionKey]] `xpack.reporting.encryptionKey` {ess-icon} | Set to an alphanumeric, at least 32 characters long text string. By default, {kib} will generate a random key when it starts, which will cause pending reports to fail after restart. Configure this setting to preserve the same key across multiple restarts and multiple instances of {kib}. @@ -53,20 +53,20 @@ proxy host requires that the {kib} server has network access to the proxy. [cols="2*<"] |=== | `xpack.reporting.kibanaServer.port` - | The port for accessing {kib}, if different from the `server.port` value. + | The port for accessing {kib}, if different from the <> value. | `xpack.reporting.kibanaServer.protocol` | The protocol for accessing {kib}, typically `http` or `https`. -| `xpack.reporting.kibanaServer.hostname` - | The hostname for accessing {kib}, if different from the `server.host` value. +|[[xpack-kibanaServer-hostname]] `xpack.reporting.kibanaServer.hostname` + | The hostname for accessing {kib}, if different from the <> value. |=== [NOTE] ============ Reporting authenticates requests on the Kibana page only when the hostname matches the -`xpack.reporting.kibanaServer.hostname` setting. Therefore Reporting would fail if the +<> setting. Therefore Reporting would fail if the set value redirects to another server. For that reason, `"0"` is an invalid setting because, in the Reporting browser, it becomes an automatic redirect to `"0.0.0.0"`. ============ @@ -97,8 +97,8 @@ reports, you might need to change the following settings. [NOTE] ============ Running multiple instances of {kib} in a cluster for load balancing of -reporting requires identical values for `xpack.reporting.encryptionKey` and, if -security is enabled, `xpack.security.encryptionKey`. +reporting requires identical values for <> and, if +security is enabled, <>. ============ [cols="2*<"] @@ -177,7 +177,7 @@ available, but there will likely be errors in the visualizations in the report. [[reporting-chromium-settings]] ==== Chromium settings -When `xpack.reporting.capture.browser.type` is set to `chromium` (default) you can also specify the following settings. +When <> is set to `chromium` (default) you can also specify the following settings. [cols="2*<"] |=== @@ -229,10 +229,10 @@ a| `xpack.reporting.capture.browser` See OWASP: https://www.owasp.org/index.php/CSV_Injection Defaults to `true`. -| `xpack.reporting.csv.enablePanelActionDownload` - | Enables CSV export from a saved search on a dashboard. This action is available in the dashboard - panel menu for the saved search. - Defaults to `true`. +| `xpack.reporting.csv` `.enablePanelActionDownload` + | Enables CSV export from a saved search on a dashboard. This action is available in the dashboard panel menu for the saved search. + *Note:* This setting exists for backwards compatibility, but is unused and hardcoded to `true`. CSV export from a saved search on a dashboard + is enabled when Reporting is enabled. |=== @@ -246,7 +246,7 @@ a| `xpack.reporting.capture.browser` | Reporting uses a weekly index in {es} to store the reporting job and the report content. The index is automatically created if it does not already exist. Configure this to a unique value, beginning with `.reporting-`, for every - {kib} instance that has a unique `kibana.index` setting. Defaults to `.reporting`. + {kib} instance that has a unique <> setting. Defaults to `.reporting`. | `xpack.reporting.roles.allow` | Specifies the roles in addition to superusers that can use reporting. diff --git a/docs/settings/security-settings.asciidoc b/docs/settings/security-settings.asciidoc index b6eecc6ea9f04..00e5f973f7d87 100644 --- a/docs/settings/security-settings.asciidoc +++ b/docs/settings/security-settings.asciidoc @@ -190,26 +190,26 @@ You can configure the following settings in the `kibana.yml` file. | `xpack.security.cookieName` | Sets the name of the cookie used for the session. The default value is `"sid"`. -| `xpack.security.encryptionKey` +|[[xpack-security-encryptionKey]] `xpack.security.encryptionKey` | An arbitrary string of 32 characters or more that is used to encrypt session information. Do **not** expose this key to users of {kib}. By default, a value is automatically generated in memory. If you use that default behavior, all sessions are invalidated when {kib} restarts. In addition, high-availability deployments of {kib} will behave unexpectedly if this setting isn't the same for all instances of {kib}. -| `xpack.security.secureCookies` +|[[xpack-security-secureCookies]] `xpack.security.secureCookies` | Sets the `secure` flag of the session cookie. The default value is `false`. It - is automatically set to `true` if `server.ssl.enabled` is set to `true`. Set + is automatically set to `true` if <> is set to `true`. Set this to `true` if SSL is configured outside of {kib} (for example, you are routing requests through a load balancer or proxy). | `xpack.security.sameSiteCookies` {ess-icon} | Sets the `SameSite` attribute of the session cookie. This allows you to declare whether your cookie should be restricted to a first-party or same-site context. Valid values are `Strict`, `Lax`, `None`. - This is *not set* by default, which modern browsers will treat as `Lax`. If you use Kibana embedded in an iframe in modern browsers, you might need to set it to `None`. Setting this value to `None` requires cookies to be sent over a secure connection by setting `xpack.security.secureCookies: true`. + This is *not set* by default, which modern browsers will treat as `Lax`. If you use Kibana embedded in an iframe in modern browsers, you might need to set it to `None`. Setting this value to `None` requires cookies to be sent over a secure connection by setting <>: `true`. -| `xpack.security.session.idleTimeout` {ess-icon} - | Ensures that user sessions will expire after a period of inactivity. This and `xpack.security.session.lifespan` are both +|[[xpack-session-idleTimeout]] `xpack.security.session.idleTimeout` {ess-icon} + | Ensures that user sessions will expire after a period of inactivity. This and <> are both highly recommended. By default, this setting is not set. 2+a| @@ -218,9 +218,9 @@ highly recommended. By default, this setting is not set. The format is a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). ============ -| `xpack.security.session.lifespan` {ess-icon} +|[[xpack-session-lifespan]] `xpack.security.session.lifespan` {ess-icon} | Ensures that user sessions will expire after the defined time period. This behavior also known as an "absolute timeout". If -this is _not_ set, user sessions could stay active indefinitely. This and `xpack.security.session.idleTimeout` are both highly +this is _not_ set, user sessions could stay active indefinitely. This and <> are both highly recommended. By default, this setting is not set. 2+a| diff --git a/docs/settings/telemetry-settings.asciidoc b/docs/settings/telemetry-settings.asciidoc index 89c018a86eca6..0329e2f010e80 100644 --- a/docs/settings/telemetry-settings.asciidoc +++ b/docs/settings/telemetry-settings.asciidoc @@ -19,7 +19,7 @@ See our https://www.elastic.co/legal/privacy-statement[Privacy Statement] to lea [cols="2*<"] |=== -| `telemetry.enabled` +|[[telemetry-enabled]] `telemetry.enabled` | Set to `true` to send cluster statistics to Elastic. Reporting your cluster statistics helps us improve your user experience. Your data is never shared with anyone. Set to `false` to disable statistics reporting from any @@ -31,16 +31,16 @@ See our https://www.elastic.co/legal/privacy-statement[Privacy Statement] to lea it is behind a firewall and falls back to `'browser'` to send it from users' browsers when they are navigating through {kib}. Defaults to `'server'`. -| `telemetry.optIn` +|[[telemetry-optIn]] `telemetry.optIn` | Set to `true` to automatically opt into reporting cluster statistics. You can also opt out through *Advanced Settings* in {kib}. Defaults to `true`. | `telemetry.allowChangingOptInStatus` - | Set to `true` to allow overwriting the `telemetry.optIn` setting via the {kib} UI. Defaults to `true`. + + | Set to `true` to allow overwriting the <> setting via the {kib} UI. Defaults to `true`. + |=== [NOTE] ============ -When `false`, `telemetry.optIn` must be `true`. To disable telemetry and not allow users to change that parameter, use `telemetry.enabled`. +When `false`, <> must be `true`. To disable telemetry and not allow users to change that parameter, use <>. ============ diff --git a/docs/setup/secure-settings.asciidoc b/docs/setup/secure-settings.asciidoc index 10380eb5d8fa4..840a18ac03bab 100644 --- a/docs/setup/secure-settings.asciidoc +++ b/docs/setup/secure-settings.asciidoc @@ -19,7 +19,7 @@ bin/kibana-keystore create ---------------------------------------------------------------- The file `kibana.keystore` will be created in the directory defined by the -`path.data` configuration setting. +<> configuration setting. [float] [[list-settings]] diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index f03022e9e9f00..af68f3e541628 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -20,11 +20,11 @@ which may cause a delay before pages start being served. Set to `false` to disable Console. *Default: `true`* | `cpu.cgroup.path.override:` - | *deprecated* This setting has been renamed to `ops.cGroupOverrides.cpuPath` + | *deprecated* This setting has been renamed to <> and the old name will no longer be supported as of 8.0. | `cpuacct.cgroup.path.override:` - | *deprecated* This setting has been renamed to `ops.cGroupOverrides.cpuAcctPath` + | *deprecated* This setting has been renamed to <> and the old name will no longer be supported as of 8.0. | `csp.rules:` @@ -33,7 +33,7 @@ that disables certain unnecessary and potentially insecure capabilities in the browser. It is strongly recommended that you keep the default CSP rules that ship with {kib}. -| `csp.strict:` +|[[csp-strict]] `csp.strict:` | Blocks {kib} access to any browser that does not enforce even rudimentary CSP rules. In practice, this disables support for older, less safe browsers like Internet Explorer. @@ -43,44 +43,44 @@ For more information, refer to <>. | `csp.warnLegacyBrowsers:` | Shows a warning message after loading {kib} to any browser that does not enforce even rudimentary CSP rules, though {kib} is still accessible. This -configuration is effectively ignored when `csp.strict` is enabled. +configuration is effectively ignored when <> is enabled. *Default: `true`* | `elasticsearch.customHeaders:` | Header names and values to send to {es}. Any custom headers cannot be overwritten by client-side headers, regardless of the -`elasticsearch.requestHeadersWhitelist` configuration. *Default: `{}`* +<> configuration. *Default: `{}`* -| `elasticsearch.hosts:` +|[[elasticsearch-hosts]] `elasticsearch.hosts:` | The URLs of the {es} instances to use for all your queries. All nodes listed here must be on the same cluster. *Default: `[ "http://localhost:9200" ]`* -+ + To enable SSL/TLS for outbound connections to {es}, use the `https` protocol in this setting. | `elasticsearch.logQueries:` - | Log queries sent to {es}. Requires `logging.verbose` set to `true`. + | Log queries sent to {es}. Requires <> set to `true`. This is useful for seeing the query DSL generated by applications that currently do not have an inspector, for example Timelion and Monitoring. *Default: `false`* -| `elasticsearch.pingTimeout:` +|[[elasticsearch-pingTimeout]] `elasticsearch.pingTimeout:` | Time in milliseconds to wait for {es} to respond to pings. -*Default: the value of the `elasticsearch.requestTimeout` setting* +*Default: the value of the <> setting* | `elasticsearch.preserveHost:` | When the value is `true`, {kib} uses the hostname specified in the -`server.host` setting. When the value is `false`, {kib} uses +<> setting. When the value is `false`, {kib} uses the hostname of the host that connects to this {kib} instance. *Default: `true`* -| `elasticsearch.requestHeadersWhitelist:` +|[[elasticsearch-requestHeadersWhitelist]] `elasticsearch.requestHeadersWhitelist:` | List of {kib} client-side headers to send to {es}. To send *no* client-side headers, set this value to [] (an empty list). Removing the `authorization` header from being whitelisted means that you cannot use <> in {kib}. *Default: `[ 'authorization' ]`* -| `elasticsearch.requestTimeout:` +|[[elasticsearch-requestTimeout]] `elasticsearch.requestTimeout:` | Time in milliseconds to wait for responses from the back end or {es}. This value must be a positive integer. *Default: `30000`* @@ -99,7 +99,7 @@ nodes. *Default: `false`* | Update the list of {es} nodes immediately following a connection fault. *Default: `false`* -| `elasticsearch.ssl.alwaysPresentCertificate:` +|[[elasticsearch-ssl-alwaysPresentCertificate]] `elasticsearch.ssl.alwaysPresentCertificate:` | Controls {kib} behavior in regard to presenting a client certificate when requested by {es}. This setting applies to all outbound SSL/TLS connections to {es}, including requests that are proxied for end users. *Default: `false`* @@ -109,7 +109,7 @@ to {es}, including requests that are proxied for end users. *Default: `false`* [WARNING] ============ When {es} uses certificates to authenticate end users with a PKI realm -and `elasticsearch.ssl.alwaysPresentCertificate` is `true`, +and <> is `true`, proxied requests may be executed as the identity that is tied to the {kib} server. ============ @@ -117,7 +117,7 @@ server. [cols="2*<"] |=== -| `elasticsearch.ssl.certificate:` and `elasticsearch.ssl.key:` +|[[elasticsearch-ssl-cert-key]] `elasticsearch.ssl.certificate:` and `elasticsearch.ssl.key:` | Paths to a PEM-encoded X.509 client certificate and its corresponding private key. These are used by {kib} to authenticate itself when making outbound SSL/TLS connections to {es}. For this setting to take effect, the @@ -129,46 +129,48 @@ be set to `"required"` or `"optional"` to request a client certificate from [NOTE] ============ -These settings cannot be used in conjunction with `elasticsearch.ssl.keystore.path`. +These settings cannot be used in conjunction with +<>. ============ [cols="2*<"] |=== -| `elasticsearch.ssl.certificateAuthorities:` +|[[elasticsearch-ssl-certificateAuthorities]] `elasticsearch.ssl.certificateAuthorities:` | Paths to one or more PEM-encoded X.509 certificate authority (CA) certificates, which make up a trusted certificate chain for {es}. This chain is used by {kib} to establish trust when making outbound SSL/TLS connections to {es}. -+ + In addition to this setting, trusted certificates may be specified via -`elasticsearch.ssl.keystore.path` and/or `elasticsearch.ssl.truststore.path`. +<> and/or +<>. | `elasticsearch.ssl.keyPassphrase:` | The password that decrypts the private key that is specified -via `elasticsearch.ssl.key`. This value is optional, as the key may not be +via <>. This value is optional, as the key may not be encrypted. -| `elasticsearch.ssl.keystore.path:` +|[[elasticsearch-ssl-keystore-path]] `elasticsearch.ssl.keystore.path:` | Path to a PKCS#12 keystore that contains an X.509 client certificate and it's corresponding private key. These are used by {kib} to authenticate itself when making outbound SSL/TLS connections to {es}. For this setting, you must also set the `xpack.security.http.ssl.client_authentication` setting in {es} to `"required"` or `"optional"` to request a client certificate from {kib}. -+ + If the keystore contains any additional certificates, they are used as a trusted certificate chain for {es}. This chain is used by {kib} to establish trust when making outbound SSL/TLS connections to {es}. In addition to this setting, trusted certificates may be specified via -`elasticsearch.ssl.certificateAuthorities` and/or -`elasticsearch.ssl.truststore.path`. +<> and/or +<>. |=== [NOTE] ============ This setting cannot be used in conjunction with -`elasticsearch.ssl.certificate` or `elasticsearch.ssl.key`. +<> or <>. ============ [cols="2*<"] @@ -176,24 +178,24 @@ This setting cannot be used in conjunction with | `elasticsearch.ssl.keystore.password:` | The password that decrypts the keystore specified via -`elasticsearch.ssl.keystore.path`. If the keystore has no password, leave this +<>. If the keystore has no password, leave this as blank. If the keystore has an empty password, set this to `""`. -| `elasticsearch.ssl.truststore.path:`:: +|[[elasticsearch-ssl-truststore-path]] `elasticsearch.ssl.truststore.path:` | Path to a PKCS#12 trust store that contains one or more X.509 certificate authority (CA) certificates, which make up a trusted certificate chain for {es}. This chain is used by {kib} to establish trust when making outbound SSL/TLS connections to {es}. -+ + In addition to this setting, trusted certificates may be specified via -`elasticsearch.ssl.certificateAuthorities` and/or -`elasticsearch.ssl.keystore.path`. +<> and/or +<>. |`elasticsearch.ssl.truststore.password:` | The password that decrypts the trust store specified via -`elasticsearch.ssl.truststore.path`. If the trust store has no password, -leave this as blank. If the trust store has an empty password, set this to `""`. +<>. If the trust store +has no password, leave this as blank. If the trust store has an empty password, set this to `""`. | `elasticsearch.ssl.verificationMode:` | Controls the verification of the server certificate that {kib} receives when @@ -206,7 +208,7 @@ verification entirely. *Default: `"full"`* | Time in milliseconds to wait for {es} at {kib} startup before retrying. *Default: `5000`* -| `elasticsearch.username:` and `elasticsearch.password:` +|[[elasticsearch-user-passwd]] `elasticsearch.username:` and `elasticsearch.password:` | If your {es} is protected with basic authentication, these settings provide the username and password that the {kib} server uses to perform maintenance on the {kib} index at startup. {kib} users still need to authenticate with @@ -220,7 +222,7 @@ on the {kib} index at startup. {kib} users still need to authenticate with Please use the `defaultRoute` advanced setting instead. The default application to load. *Default: `"home"`* -| `kibana.index:` +|[[kibana-index]] `kibana.index:` | {kib} uses an index in {es} to store saved searches, visualizations, and dashboards. {kib} creates a new index if the index doesn’t already exist. If you configure a custom index, the name must be lowercase, and conform to the @@ -236,7 +238,7 @@ This value must be a whole number greater than zero. *Default: `"1000"`* suggestions. This value must be a whole number greater than zero. *Default: `"100000"`* -| `logging.dest:` +|[[logging-dest]] `logging.dest:` | Enables you to specify a file where {kib} stores log output. *Default: `stdout`* @@ -244,7 +246,7 @@ suggestions. This value must be a whole number greater than zero. | Logs output as JSON. When set to `true`, the logs are formatted as JSON strings that include timestamp, log level, context, message text, and any other metadata that may be associated with the log message. -When `logging.dest.stdout` is set, and there is no interactive terminal ("TTY"), +When <> is set, and there is no interactive terminal ("TTY"), this setting defaults to `true`. *Default: `false`* | `logging.quiet:` @@ -271,7 +273,7 @@ The following example shows a valid logging rotate configuration: | `logging.rotate.enabled:` | experimental[] Set the value of this setting to `true` to -enable log rotation. If you do not have a `logging.dest` set that is different from `stdout` +enable log rotation. If you do not have a <> set that is different from `stdout` that feature would not take any effect. *Default: `false`* | `logging.rotate.everyBytes:` @@ -286,9 +288,9 @@ option has to be in the range of 2 to 1024 files. *Default: `7`* | `logging.rotate.pollingInterval:` | experimental[] The number of milliseconds for the polling strategy in case -the `logging.rotate.usePolling` is enabled. `logging.rotate.usePolling` must be in the 5000 to 3600000 millisecond range. *Default: `10000`* +the <> is enabled. `logging.rotate.usePolling` must be in the 5000 to 3600000 millisecond range. *Default: `10000`* -| `logging.rotate.usePolling:` +|[[logging-rotate-usePolling]] `logging.rotate.usePolling:` | experimental[] By default we try to understand the best way to monitoring the log file and warning about it. Please be aware there are some systems where watch api is not accurate. In those cases, in order to get the feature working, the `polling` method could be used enabling that option. *Default: `false`* @@ -298,24 +300,25 @@ the `polling` method could be used enabling that option. *Default: `false`* suppress all logging output. *Default: `false`* | `logging.timezone` - | Set to the canonical timezone ID -(for example, `America/Los_Angeles`) to log events using that timezone. For a -list of timezones, refer to https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. *Default: `UTC`* + | Set to the canonical time zone ID +(for example, `America/Los_Angeles`) to log events using that time zone. +For possible values, refer to +https://en.wikipedia.org/wiki/List_of_tz_database_time_zones[database time zones]. *Default: `UTC`* -| [[logging-verbose]] `logging.verbose:` {ece-icon} +| [[logging-verbose]] `logging.verbose:` {ess-icon} | Set to `true` to log all events, including system usage information and all requests. *Default: `false`* -| `map.includeElasticMapsService:` {ess-icon} +| [[regionmap-ES-map]] `map.includeElasticMapsService:` {ess-icon} | Set to `false` to disable connections to Elastic Maps Service. -When `includeElasticMapsService` is turned off, only the vector layers configured by `map.regionmap` -and the tile layer configured by `map.tilemap.url` are available in <>. *Default: `true`* +When `includeElasticMapsService` is turned off, only the vector layers configured by <> +and the tile layer configured by <> are available in <>. *Default: `true`* | `map.proxyElasticMapsServiceInMaps:` | Set to `true` to proxy all <> Elastic Maps Service requests through the {kib} server. *Default: `false`* -| [[regionmap-settings]] `map.regionmap:` {ess-icon} {ece-icon} +| [[regionmap-settings]] `map.regionmap:` {ess-icon} | Specifies additional vector layers for use in <> visualizations. Each layer object points to an external vector file that contains a geojson @@ -345,16 +348,10 @@ map.regionmap: [cols="2*<"] |=== -| [[regionmap-ES-map]] `map.includeElasticMapsService:` {ece-icon} - | Turns on or off whether layers from the Elastic Maps Service should be included in the vector -layer option list. By turning this off, -only the layers that are configured here will be included. The default is `true`. -This also affects whether tile-service from the Elastic Maps Service will be available. - -| [[regionmap-attribution]] `map.regionmap.layers[].attribution:` {ess-icon} {ece-icon} +| [[regionmap-attribution]] `map.regionmap.layers[].attribution:` {ess-icon} | Optional. References the originating source of the geojson file. -| [[regionmap-fields]] `map.regionmap.layers[].fields[]:` {ess-icon} {ece-icon} +| [[regionmap-fields]] `map.regionmap.layers[].fields[]:` {ess-icon} | 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 @@ -380,11 +377,11 @@ map.regionmap: [cols="2*<"] |=== -| [[regionmap-field-description]] `map.regionmap.layers[].fields[].description:` {ess-icon} {ece-icon} +| [[regionmap-field-description]] `map.regionmap.layers[].fields[].description:` {ess-icon} | 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} {ece-icon} +| [[regionmap-field-name]] `map.regionmap.layers[].fields[].name:` {ess-icon} | 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 @@ -392,30 +389,30 @@ 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} {ece-icon} +| [[regionmap-name]] `map.regionmap.layers[].name:` {ess-icon} | Mandatory. A description of the map being provided. -| [[regionmap-url]] `map.regionmap.layers[].url:` {ess-icon} {ece-icon} +| [[regionmap-url]] `map.regionmap.layers[].url:` {ess-icon} | Mandatory. The location of the geojson file as provided by a webserver. -| [[tilemap-settings]] `map.tilemap.options.attribution:` {ess-icon} {ece-icon} +| [[tilemap-settings]] `map.tilemap.options.attribution:` {ess-icon} | The map attribution string. *Default: `"© [Elastic Maps Service](https://www.elastic.co/elastic-maps-service)"`* -| [[tilemap-max-zoom]] `map.tilemap.options.maxZoom:` {ess-icon} {ece-icon} +| [[tilemap-max-zoom]] `map.tilemap.options.maxZoom:` {ess-icon} | The maximum zoom level. *Default: `10`* -| [[tilemap-min-zoom]] `map.tilemap.options.minZoom:` {ess-icon} {ece-icon} +| [[tilemap-min-zoom]] `map.tilemap.options.minZoom:` {ess-icon} | The minimum zoom level. *Default: `1`* -| [[tilemap-subdomains]] `map.tilemap.options.subdomains:` {ess-icon} {ece-icon} +| [[tilemap-subdomains]] `map.tilemap.options.subdomains:` {ess-icon} | An array of subdomains used by the tile service. Specify the position of the subdomain the URL with the token `{s}`. -| [[tilemap-url]] `map.tilemap.url:` {ess-icon} {ece-icon} +| [[tilemap-url]] `map.tilemap.url:` {ess-icon} | The URL to the tileservice that {kib} uses to display map tiles in tilemap visualizations. By default, {kib} reads this URL from an external metadata service, but users can @@ -427,7 +424,7 @@ override this parameter to use their own Tile Map Service. For example: system for the {kib} UI notification center. Set to `false` to disable the newsfeed system. *Default: `true`* -| `path.data:` +|[[path-data]] `path.data:` | The path where {kib} stores persistent data not saved in {es}. *Default: `data`* @@ -438,17 +435,17 @@ not saved in {es}. *Default: `data`* | Set the interval in milliseconds to sample system and process performance metrics. The minimum value is 100. *Default: `5000`* -| `ops.cGroupOverrides.cpuPath:` +|[[ops-cGroupOverrides-cpuPath]] `ops.cGroupOverrides.cpuPath:` | Override for cgroup cpu path when mounted in a manner that is inconsistent with `/proc/self/cgroup`. -| `ops.cGroupOverrides.cpuAcctPath:` +|[[ops-cGroupOverrides-cpuAcctPath]] `ops.cGroupOverrides.cpuAcctPath:` | Override for cgroup cpuacct path when mounted in a manner that is inconsistent with `/proc/self/cgroup`. -| `server.basePath:` +|[[server-basePath]] `server.basePath:` | Enables you to specify a path to mount {kib} at if you are -running behind a proxy. Use the `server.rewriteBasePath` setting to tell {kib} +running behind a proxy. Use the <> setting to tell {kib} if it should remove the basePath from requests it receives, and to prevent a deprecation warning at startup. This setting cannot end in a slash (`/`). @@ -458,19 +455,19 @@ deprecation warning at startup. This setting cannot end in a slash (`/`). | `server.compression.referrerWhitelist:` | Specifies an array of trusted hostnames, such as the {kib} host, or a reverse proxy sitting in front of it. This determines whether HTTP compression may be used for responses, based on the request `Referer` header. -This setting may not be used when `server.compression.enabled` is set to `false`. *Default: `none`* +This setting may not be used when <> is set to `false`. *Default: `none`* | `server.customResponseHeaders:` {ess-icon} | Header names and values to send on all responses to the client from the {kib} server. *Default: `{}`* -| `server.host:` +|[[server-host]] `server.host:` | This setting specifies the host of the back end server. To allow remote users to connect, set the value to the IP address or DNS name of the {kib} server. *Default: `"localhost"`* | `server.keepaliveTimeout:` | The number of milliseconds to wait for additional data before restarting -the `server.socketTimeout` counter. *Default: `"120000"`* +the <> counter. *Default: `"120000"`* | `server.maxPayloadBytes:` | The maximum payload size in bytes @@ -480,28 +477,28 @@ for incoming server requests. *Default: `1048576`* | A human-readable display name that identifies this {kib} instance. *Default: `"your-hostname"`* -| `server.port:` +|[[server-port]] `server.port:` | {kib} is served by a back end server. This setting specifies the port to use. *Default: `5601`* -| `server.requestId.allowFromAnyIp:` +|[[server-requestId-allowFromAnyIp]] `server.requestId.allowFromAnyIp:` | Sets whether or not the X-Opaque-Id header should be trusted from any IP address for identifying requests in logs and forwarded to Elasticsearch. | `server.requestId.ipAllowlist:` - | A list of IPv4 and IPv6 address which the `X-Opaque-Id` header should be trusted from. Normally this would be set to the IP addresses of the load balancers or reverse-proxy that end users use to access Kibana. If any are set, `server.requestId.allowFromAnyIp` must also be set to `false.` + | A list of IPv4 and IPv6 address which the `X-Opaque-Id` header should be trusted from. Normally this would be set to the IP addresses of the load balancers or reverse-proxy that end users use to access Kibana. If any are set, <> must also be set to `false.` -| `server.rewriteBasePath:` +|[[server-rewriteBasePath]] `server.rewriteBasePath:` | Specifies whether {kib} should -rewrite requests that are prefixed with `server.basePath` or require that they +rewrite requests that are prefixed with <> or require that they are rewritten by your reverse proxy. In {kib} 6.3 and earlier, the default is `false`. In {kib} 7.x, the setting is deprecated. In {kib} 8.0 and later, the default is `true`. *Default: `deprecated`* -| `server.socketTimeout:` +|[[server-socketTimeout]] `server.socketTimeout:` | The number of milliseconds to wait before closing an inactive socket. *Default: `"120000"`* -| `server.ssl.certificate:` and `server.ssl.key:` +|[[server-ssl-cert-key]] `server.ssl.certificate:` and `server.ssl.key:` | Paths to a PEM-encoded X.509 server certificate and its corresponding private key. These are used by {kib} to establish trust when receiving inbound SSL/TLS connections from users. @@ -509,18 +506,18 @@ are used by {kib} to establish trust when receiving inbound SSL/TLS connections [NOTE] ============ -These settings cannot be used in conjunction with `server.ssl.keystore.path`. +These settings cannot be used in conjunction with <>. ============ [cols="2*<"] |=== -| `server.ssl.certificateAuthorities:` +|[[server-ssl-certificateAuthorities]] `server.ssl.certificateAuthorities:` | Paths to one or more PEM-encoded X.509 certificate authority (CA) certificates which make up a trusted certificate chain for {kib}. This chain is used by {kib} to establish trust when receiving inbound SSL/TLS connections from end users. If PKI authentication is enabled, this chain is also used by {kib} to verify client certificates from end users. -+ -In addition to this setting, trusted certificates may be specified via `server.ssl.keystore.path` and/or `server.ssl.truststore.path`. + +In addition to this setting, trusted certificates may be specified via <> and/or <>. | `server.ssl.cipherSuites:` | Details on the format, and the valid options, are available via the @@ -533,53 +530,53 @@ connections. Valid values are `"required"`, `"optional"`, and `"none"`. Using `" client presents a certificate, using `"optional"` will allow a client to present a certificate if it has one, and using `"none"` will prevent a client from presenting a certificate. *Default: `"none"`* -| `server.ssl.enabled:` +|[[server-ssl-enabled]] `server.ssl.enabled:` | Enables SSL/TLS for inbound connections to {kib}. When set to `true`, a certificate and its -corresponding private key must be provided. These can be specified via `server.ssl.keystore.path` or the combination of -`server.ssl.certificate` and `server.ssl.key`. *Default: `false`* +corresponding private key must be provided. These can be specified via <> or the combination of +<> and <>. *Default: `false`* | `server.ssl.keyPassphrase:` - | The password that decrypts the private key that is specified via `server.ssl.key`. This value + | The password that decrypts the private key that is specified via <>. This value is optional, as the key may not be encrypted. -| `server.ssl.keystore.path:` +|[[server-ssl-keystore-path]] `server.ssl.keystore.path:` | Path to a PKCS#12 keystore that contains an X.509 server certificate and its corresponding private key. If the keystore contains any additional certificates, those will be used as a trusted certificate chain for {kib}. All of these are used by {kib} to establish trust when receiving inbound SSL/TLS connections from end users. The certificate chain is also used by {kib} to verify client certificates from end users when PKI authentication is enabled. -+ -In addition to this setting, trusted certificates may be specified via `server.ssl.certificateAuthorities` and/or -`server.ssl.truststore.path`. + +In addition to this setting, trusted certificates may be specified via <> and/or +<>. |=== [NOTE] ============ -This setting cannot be used in conjunction with `server.ssl.certificate` or `server.ssl.key` +This setting cannot be used in conjunction with <> or <> ============ [cols="2*<"] |=== | `server.ssl.keystore.password:` - | The password that will be used to decrypt the keystore specified via `server.ssl.keystore.path`. If the + | The password that will be used to decrypt the keystore specified via <>. If the keystore has no password, leave this unset. If the keystore has an empty password, set this to `""`. -| `server.ssl.truststore.path:` +|[[server-ssl-truststore-path]] `server.ssl.truststore.path:` | Path to a PKCS#12 trust store that contains one or more X.509 certificate authority (CA) certificates which make up a trusted certificate chain for {kib}. This chain is used by {kib} to establish trust when receiving inbound SSL/TLS connections from end users. If PKI authentication is enabled, this chain is also used by {kib} to verify client certificates from end users. -+ -In addition to this setting, trusted certificates may be specified via `server.ssl.certificateAuthorities` and/or -`server.ssl.keystore.path`. + +In addition to this setting, trusted certificates may be specified via <> and/or +<>. | `server.ssl.truststore.password:` - | The password that will be used to decrypt the trust store specified via `server.ssl.truststore.path`. If + | The password that will be used to decrypt the trust store specified via <>. If the trust store has no password, leave this unset. If the trust store has an empty password, set this to `""`. | `server.ssl.redirectHttpFromPort:` | {kib} binds to this port and redirects -all http requests to https over the port configured as `server.port`. +all http requests to https over the port configured as <>. | `server.ssl.supportedProtocols:` | An array of supported protocols with versions. @@ -588,7 +585,7 @@ Valid protocols: `TLSv1`, `TLSv1.1`, `TLSv1.2`. *Default: TLSv1.1, TLSv1.2* | [[settings-xsrf-whitelist]] `server.xsrf.whitelist:` | It is not recommended to disable protections for arbitrary API endpoints. Instead, supply the `kbn-xsrf` header. -The `server.xsrf.whitelist` setting requires the following format: +The <> setting requires the following format: |=== @@ -608,18 +605,18 @@ The `server.xsrf.whitelist` setting requires the following format: setting this to `true` enables unauthenticated users to access the {kib} server status API and status page. *Default: `false`* -| `telemetry.allowChangingOptInStatus` +|[[telemetry-allowChangingOptInStatus]] `telemetry.allowChangingOptInStatus` | When `true`, users are able to change the telemetry setting at a later time in <>. When `false`, -{kib} looks at the value of `telemetry.optIn` to determine whether to send -telemetry data or not. `telemetry.allowChangingOptInStatus` and `telemetry.optIn` +{kib} looks at the value of <> to determine whether to send +telemetry data or not. <> and <> cannot be `false` at the same time. *Default: `true`*. -| `telemetry.optIn` +|[[settings-telemetry-optIn]] `telemetry.optIn` | When `true`, telemetry data is sent to Elastic. When `false`, collection of telemetry data is disabled. To enable telemetry and prevent users from disabling it, -set `telemetry.allowChangingOptInStatus` to `false` and `telemetry.optIn` to `true`. +set <> to `false` and <> to `true`. *Default: `true`* | `telemetry.enabled` diff --git a/docs/user/dashboard/url-drilldown.asciidoc b/docs/user/dashboard/url-drilldown.asciidoc index e6daf89d72718..ee879256a1fae 100644 --- a/docs/user/dashboard/url-drilldown.asciidoc +++ b/docs/user/dashboard/url-drilldown.asciidoc @@ -238,3 +238,14 @@ Tip: Consider using <> helper for date formatting. | Aggregation field behind the selected range, if available. |=== + +[float] +[[disable]] +==== Disable URL drilldown + +You can disable URL drilldown feature on your {kib} instance by disabling the plugin: + +["source","yml"] +----------- +url_drilldown.enabled: false +----------- diff --git a/docs/user/management.asciidoc b/docs/user/management.asciidoc index bc96463f6efba..e0d550a15a907 100644 --- a/docs/user/management.asciidoc +++ b/docs/user/management.asciidoc @@ -58,12 +58,12 @@ years of historical data in combination with your raw data. | {ref}/transforms.html[Transforms] |Use transforms to pivot existing {es} indices into summarized or entity-centric indices. -| <> +| {ref}/ccr-getting-started.html[Cross-Cluster Replication] |Replicate indices on a remote cluster and copy them to a follower index on a local cluster. This is important for disaster recovery. It also keeps data local for faster queries. -| <> +| {ref}/ccr-getting-started.html#ccr-getting-started-remote-cluster[Remote Clusters] |Manage your remote clusters for use with cross-cluster search and cross-cluster replication. You can add and remove remote clusters, and check their connectivity. |=== @@ -180,8 +180,6 @@ include::{kib-repo-dir}/management/alerting/connector-management.asciidoc[] include::{kib-repo-dir}/management/managing-beats.asciidoc[] -include::{kib-repo-dir}/management/managing-ccr.asciidoc[] - include::{kib-repo-dir}/management/index-lifecycle-policies/intro-to-lifecycle-policies.asciidoc[] include::{kib-repo-dir}/management/index-lifecycle-policies/create-policy.asciidoc[] @@ -200,8 +198,6 @@ include::{kib-repo-dir}/management/managing-licenses.asciidoc[] include::{kib-repo-dir}/management/numeral.asciidoc[] -include::{kib-repo-dir}/management/managing-remote-clusters.asciidoc[] - include::{kib-repo-dir}/management/rollups/create_and_manage_rollups.asciidoc[] include::{kib-repo-dir}/management/managing-saved-objects.asciidoc[] diff --git a/docs/user/monitoring/viewing-metrics.asciidoc b/docs/user/monitoring/viewing-metrics.asciidoc index f35caea025cdd..0c48e3b7d011d 100644 --- a/docs/user/monitoring/viewing-metrics.asciidoc +++ b/docs/user/monitoring/viewing-metrics.asciidoc @@ -13,13 +13,19 @@ At a minimum, you must have monitoring data for the {es} production cluster. Once that data exists, {kib} can display monitoring data for other products in the cluster. +TIP: If you use a separate monitoring cluster to store the monitoring data, it +is strongly recommended that you use a separate {kib} instance to view it. If +you log in to {kib} using SAML, Kerberos, PKI, OpenID Connect, or token +authentication providers, a dedicated {kib} instance is *required*. The security +tokens that are used in these contexts are cluster-specific, therefore you +cannot use a single {kib} instance to connect to both production and monitoring +clusters. For more information about the recommended configuration, see +{ref}/monitoring-overview.html[Monitoring overview]. + . Identify where to retrieve monitoring data from. + -- -The cluster that contains the monitoring data is referred to -as the _monitoring cluster_. - -TIP: If the monitoring data is stored on a *dedicated* monitoring cluster, it is +If the monitoring data is stored on a dedicated monitoring cluster, it is accessible even when the cluster you're monitoring is not. If you have at least a gold license, you can send data from multiple clusters to the same monitoring cluster and view them all through the same instance of {kib}. diff --git a/examples/search_examples/public/components/app.tsx b/examples/search_examples/public/components/app.tsx index 704d31d42e640..ab0ce185f0602 100644 --- a/examples/search_examples/public/components/app.tsx +++ b/examples/search_examples/public/components/app.tsx @@ -56,6 +56,8 @@ import { IndexPatternSelect, IndexPattern, IndexPatternField, + isCompleteResponse, + isErrorResponse, } from '../../../../src/plugins/data/public'; interface SearchExamplesAppDeps { @@ -144,7 +146,7 @@ export const SearchExamplesApp = ({ }) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { setTimeTook(response.rawResponse.took); const avgResult: number | undefined = response.rawResponse.aggregations ? response.rawResponse.aggregations[1].value @@ -162,7 +164,7 @@ export const SearchExamplesApp = ({ text: mountReactNode(message), }); searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { // TODO: Make response error status clearer notifications.toasts.addWarning('An error has occurred'); searchSubscription$.unsubscribe(); diff --git a/kibana.d.ts b/kibana.d.ts index b707405ffbeaf..50f8b8690d944 100644 --- a/kibana.d.ts +++ b/kibana.d.ts @@ -28,7 +28,6 @@ export { Public, Server }; /** * All exports from TS ambient definitions (where types are added for JS source in a .d.ts file). */ -import * as LegacyKibanaPluginSpec from './src/legacy/plugin_discovery/plugin_spec/plugin_spec_options'; import * as LegacyKibanaServer from './src/legacy/server/kbn_server'; /** @@ -39,8 +38,4 @@ export namespace Legacy { export type Request = LegacyKibanaServer.Request; export type ResponseToolkit = LegacyKibanaServer.ResponseToolkit; export type Server = LegacyKibanaServer.Server; - - export type InitPluginFunction = LegacyKibanaPluginSpec.InitPluginFunction; - export type UiExports = LegacyKibanaPluginSpec.UiExports; - export type PluginSpecOptions = LegacyKibanaPluginSpec.PluginSpecOptions; } diff --git a/package.json b/package.json index b990c797425f1..a296e447c6823 100644 --- a/package.json +++ b/package.json @@ -350,7 +350,7 @@ "babel-eslint": "^10.0.3", "babel-jest": "^25.5.1", "babel-plugin-istanbul": "^6.0.0", - "backport": "5.5.1", + "backport": "5.6.0", "brace": "0.11.1", "chai": "3.5.0", "chance": "1.0.18", diff --git a/src/cli/serve/serve.js b/src/cli/serve/serve.js index d8bd39b9dcdf4..a1715cf3dba2c 100644 --- a/src/cli/serve/serve.js +++ b/src/cli/serve/serve.js @@ -48,11 +48,6 @@ const CAN_CLUSTER = canRequire(CLUSTER_MANAGER_PATH); const REPL_PATH = resolve(__dirname, '../repl'); const CAN_REPL = canRequire(REPL_PATH); -// xpack is installed in both dev and the distributable, it's optional if -// install is a link to the source, not an actual install -const XPACK_DIR = resolve(__dirname, '../../../x-pack'); -const XPACK_INSTALLED = canRequire(XPACK_DIR); - const pathCollector = function () { const paths = []; return function (path) { @@ -137,16 +132,7 @@ function applyConfigOverrides(rawConfig, opts, extraCliOptions) { if (opts.logFile) set('logging.dest', opts.logFile); set('plugins.scanDirs', _.compact([].concat(get('plugins.scanDirs'), opts.pluginDir))); - set( - 'plugins.paths', - _.compact( - [].concat( - get('plugins.paths'), - opts.pluginPath, - XPACK_INSTALLED && !opts.oss ? [XPACK_DIR] : [] - ) - ) - ); + set('plugins.paths', _.compact([].concat(get('plugins.paths'), opts.pluginPath))); merge(extraCliOptions); merge(readKeystore()); diff --git a/src/core/public/injected_metadata/injected_metadata_service.ts b/src/core/public/injected_metadata/injected_metadata_service.ts index 5b51bc823d166..bd8c9e91f15a2 100644 --- a/src/core/public/injected_metadata/injected_metadata_service.ts +++ b/src/core/public/injected_metadata/injected_metadata_service.ts @@ -58,19 +58,6 @@ export interface InjectedMetadataParams { uiPlugins: InjectedPluginMetadata[]; anonymousStatusPage: boolean; legacyMetadata: { - app: { - id: string; - title: string; - }; - bundleId: string; - version: string; - branch: string; - buildNum: number; - buildSha: string; - basePath: string; - serverName: string; - devMode: boolean; - category?: AppCategory; uiSettings: { defaults: Record; user?: Record; @@ -167,18 +154,6 @@ export interface InjectedMetadataSetup { getPlugins: () => InjectedPluginMetadata[]; getAnonymousStatusPage: () => boolean; getLegacyMetadata: () => { - app: { - id: string; - title: string; - }; - bundleId: string; - version: string; - branch: string; - buildNum: number; - buildSha: string; - basePath: string; - serverName: string; - devMode: boolean; uiSettings: { defaults: Record; user?: Record | undefined; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 1c17be50454c5..7179c6cf8b133 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -1079,6 +1079,7 @@ export interface SavedObjectsFindOptions { sortOrder?: string; // (undocumented) type: string | string[]; + typeToNamespacesMap?: Map; } // @public diff --git a/src/core/public/saved_objects/saved_objects_client.ts b/src/core/public/saved_objects/saved_objects_client.ts index 5a8949ca2f55f..6a10eb44d9ca4 100644 --- a/src/core/public/saved_objects/saved_objects_client.ts +++ b/src/core/public/saved_objects/saved_objects_client.ts @@ -34,7 +34,7 @@ import { HttpFetchOptions, HttpSetup } from '../http'; type SavedObjectsFindOptions = Omit< SavedObjectFindOptionsServer, - 'namespace' | 'sortOrder' | 'rootSearchFields' + 'sortOrder' | 'rootSearchFields' | 'typeToNamespacesMap' >; type PromiseType> = T extends Promise ? U : never; diff --git a/src/core/server/index.ts b/src/core/server/index.ts index e136c699f7246..70ef93963c69f 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -339,14 +339,7 @@ export { SavedObjectsMigrationVersion, } from './types'; -export { - LegacyServiceSetupDeps, - LegacyServiceStartDeps, - LegacyServiceDiscoverPlugins, - LegacyConfig, - LegacyUiExports, - LegacyInternals, -} from './legacy'; +export { LegacyServiceSetupDeps, LegacyServiceStartDeps, LegacyConfig } from './legacy'; export { CoreStatus, diff --git a/src/core/server/legacy/config/ensure_valid_configuration.test.ts b/src/core/server/legacy/config/ensure_valid_configuration.test.ts index 702840b8a0a6a..700fe69954655 100644 --- a/src/core/server/legacy/config/ensure_valid_configuration.test.ts +++ b/src/core/server/legacy/config/ensure_valid_configuration.test.ts @@ -39,17 +39,12 @@ describe('ensureValidConfiguration', () => { configService as any, { settings: 'settings', - pluginSpecs: 'pluginSpecs', - disabledPluginSpecs: 'disabledPluginSpecs', - pluginExtendedConfig: 'pluginExtendedConfig', - uiExports: 'uiExports', + legacyConfig: 'pluginExtendedConfig', } as any ); expect(getUnusedConfigKeys).toHaveBeenCalledTimes(1); expect(getUnusedConfigKeys).toHaveBeenCalledWith({ coreHandledConfigPaths: ['core', 'elastic'], - pluginSpecs: 'pluginSpecs', - disabledPluginSpecs: 'disabledPluginSpecs', settings: 'settings', legacyConfig: 'pluginExtendedConfig', }); diff --git a/src/core/server/legacy/config/ensure_valid_configuration.ts b/src/core/server/legacy/config/ensure_valid_configuration.ts index 5cd1603ea65fb..34f98b9b3a795 100644 --- a/src/core/server/legacy/config/ensure_valid_configuration.ts +++ b/src/core/server/legacy/config/ensure_valid_configuration.ts @@ -19,19 +19,17 @@ import { getUnusedConfigKeys } from './get_unused_config_keys'; import { ConfigService } from '../../config'; -import { LegacyServiceDiscoverPlugins } from '../types'; import { CriticalError } from '../../errors'; +import { LegacyServiceSetupConfig } from '../types'; export async function ensureValidConfiguration( configService: ConfigService, - { pluginSpecs, disabledPluginSpecs, pluginExtendedConfig, settings }: LegacyServiceDiscoverPlugins + { legacyConfig, settings }: LegacyServiceSetupConfig ) { const unusedConfigKeys = await getUnusedConfigKeys({ coreHandledConfigPaths: await configService.getUsedPaths(), - pluginSpecs, - disabledPluginSpecs, settings, - legacyConfig: pluginExtendedConfig, + legacyConfig, }); if (unusedConfigKeys.length > 0) { diff --git a/src/core/server/legacy/config/get_unused_config_keys.test.ts b/src/core/server/legacy/config/get_unused_config_keys.test.ts index f8506b5744030..6ce69fca0270a 100644 --- a/src/core/server/legacy/config/get_unused_config_keys.test.ts +++ b/src/core/server/legacy/config/get_unused_config_keys.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { LegacyPluginSpec, LegacyConfig, LegacyVars } from '../types'; +import { LegacyConfig, LegacyVars } from '../types'; import { getUnusedConfigKeys } from './get_unused_config_keys'; describe('getUnusedConfigKeys', () => { @@ -35,8 +35,6 @@ describe('getUnusedConfigKeys', () => { expect( await getUnusedConfigKeys({ coreHandledConfigPaths: [], - pluginSpecs: [], - disabledPluginSpecs: [], settings: {}, legacyConfig: getConfig(), }) @@ -47,8 +45,6 @@ describe('getUnusedConfigKeys', () => { expect( await getUnusedConfigKeys({ coreHandledConfigPaths: [], - pluginSpecs: [], - disabledPluginSpecs: [], settings: { presentInBoth: true, alsoInBoth: 'someValue', @@ -65,8 +61,6 @@ describe('getUnusedConfigKeys', () => { expect( await getUnusedConfigKeys({ coreHandledConfigPaths: [], - pluginSpecs: [], - disabledPluginSpecs: [], settings: { presentInBoth: true, }, @@ -82,8 +76,6 @@ describe('getUnusedConfigKeys', () => { expect( await getUnusedConfigKeys({ coreHandledConfigPaths: [], - pluginSpecs: [], - disabledPluginSpecs: [], settings: { presentInBoth: true, onlyInSetting: 'value', @@ -99,8 +91,6 @@ describe('getUnusedConfigKeys', () => { expect( await getUnusedConfigKeys({ coreHandledConfigPaths: [], - pluginSpecs: [], - disabledPluginSpecs: [], settings: { elasticsearch: { username: 'foo', @@ -121,8 +111,6 @@ describe('getUnusedConfigKeys', () => { expect( await getUnusedConfigKeys({ coreHandledConfigPaths: [], - pluginSpecs: [], - disabledPluginSpecs: [], settings: { env: 'development', }, @@ -139,8 +127,6 @@ describe('getUnusedConfigKeys', () => { expect( await getUnusedConfigKeys({ coreHandledConfigPaths: [], - pluginSpecs: [], - disabledPluginSpecs: [], settings: { prop: ['a', 'b', 'c'], }, @@ -152,40 +138,10 @@ describe('getUnusedConfigKeys', () => { }); }); - it('ignores config for plugins that are disabled', async () => { - expect( - await getUnusedConfigKeys({ - coreHandledConfigPaths: [], - pluginSpecs: [], - disabledPluginSpecs: [ - ({ - id: 'foo', - getConfigPrefix: () => 'foo.bar', - } as unknown) as LegacyPluginSpec, - ], - settings: { - foo: { - bar: { - unused: true, - }, - }, - plugin: { - missingProp: false, - }, - }, - legacyConfig: getConfig({ - prop: 'a', - }), - }) - ).toEqual(['plugin.missingProp']); - }); - it('ignores properties managed by the new platform', async () => { expect( await getUnusedConfigKeys({ coreHandledConfigPaths: ['core', 'foo.bar'], - pluginSpecs: [], - disabledPluginSpecs: [], settings: { core: { prop: 'value', @@ -204,8 +160,6 @@ describe('getUnusedConfigKeys', () => { expect( await getUnusedConfigKeys({ coreHandledConfigPaths: ['core', 'array'], - pluginSpecs: [], - disabledPluginSpecs: [], settings: { core: { prop: 'value', diff --git a/src/core/server/legacy/config/get_unused_config_keys.ts b/src/core/server/legacy/config/get_unused_config_keys.ts index c15c3b270df05..5bbe169033e39 100644 --- a/src/core/server/legacy/config/get_unused_config_keys.ts +++ b/src/core/server/legacy/config/get_unused_config_keys.ts @@ -19,30 +19,20 @@ import { difference } from 'lodash'; import { getFlattenedObject } from '@kbn/std'; -import { unset } from '../../../../legacy/utils'; import { hasConfigPathIntersection } from '../../config'; -import { LegacyPluginSpec, LegacyConfig, LegacyVars } from '../types'; +import { LegacyConfig, LegacyVars } from '../types'; const getFlattenedKeys = (object: object) => Object.keys(getFlattenedObject(object)); export async function getUnusedConfigKeys({ coreHandledConfigPaths, - pluginSpecs, - disabledPluginSpecs, settings, legacyConfig, }: { coreHandledConfigPaths: string[]; - pluginSpecs: LegacyPluginSpec[]; - disabledPluginSpecs: LegacyPluginSpec[]; settings: LegacyVars; legacyConfig: LegacyConfig; }) { - // remove config values from disabled plugins - for (const spec of disabledPluginSpecs) { - unset(settings, spec.getConfigPrefix()); - } - const inputKeys = getFlattenedKeys(settings); const appliedKeys = getFlattenedKeys(legacyConfig.get()); diff --git a/src/core/server/legacy/index.ts b/src/core/server/legacy/index.ts index 6b0963e3129c6..1a0bc8955be0f 100644 --- a/src/core/server/legacy/index.ts +++ b/src/core/server/legacy/index.ts @@ -20,8 +20,6 @@ /** @internal */ export { ensureValidConfiguration } from './config'; /** @internal */ -export { LegacyInternals } from './legacy_internals'; -/** @internal */ export { LegacyService, ILegacyService } from './legacy_service'; /** @internal */ export * from './types'; diff --git a/src/core/server/legacy/legacy_internals.test.ts b/src/core/server/legacy/legacy_internals.test.ts deleted file mode 100644 index 935e36a989a0c..0000000000000 --- a/src/core/server/legacy/legacy_internals.test.ts +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Server } from 'hapi'; - -import { configMock } from '../config/mocks'; -import { httpServiceMock } from '../http/http_service.mock'; -import { httpServerMock } from '../http/http_server.mocks'; -import { findLegacyPluginSpecsMock } from './legacy_service.test.mocks'; -import { LegacyInternals } from './legacy_internals'; -import { ILegacyInternals, LegacyConfig, LegacyVars, LegacyUiExports } from './types'; - -function varsProvider(vars: LegacyVars, configValue?: any) { - return { - fn: jest.fn().mockReturnValue(vars), - pluginSpec: { - readConfigValue: jest.fn().mockReturnValue(configValue), - }, - }; -} - -describe('LegacyInternals', () => { - describe('getInjectedUiAppVars()', () => { - let uiExports: LegacyUiExports; - let config: LegacyConfig; - let server: Server; - let legacyInternals: ILegacyInternals; - - beforeEach(async () => { - uiExports = findLegacyPluginSpecsMock().uiExports; - config = configMock.create() as any; - server = httpServiceMock.createInternalSetupContract().server; - legacyInternals = new LegacyInternals(uiExports, config, server); - }); - - it('gets with no injectors', async () => { - await expect(legacyInternals.getInjectedUiAppVars('core')).resolves.toMatchInlineSnapshot( - `Object {}` - ); - }); - - it('gets with no matching injectors', async () => { - const injector = jest.fn().mockResolvedValue({ not: 'core' }); - legacyInternals.injectUiAppVars('not-core', injector); - - await expect(legacyInternals.getInjectedUiAppVars('core')).resolves.toMatchInlineSnapshot( - `Object {}` - ); - expect(injector).not.toHaveBeenCalled(); - }); - - it('gets with single matching injector', async () => { - const injector = jest.fn().mockResolvedValue({ is: 'core' }); - legacyInternals.injectUiAppVars('core', injector); - - await expect(legacyInternals.getInjectedUiAppVars('core')).resolves.toMatchInlineSnapshot(` - Object { - "is": "core", - } - `); - expect(injector).toHaveBeenCalled(); - }); - - it('gets with multiple matching injectors', async () => { - const injectors = [ - jest.fn().mockResolvedValue({ is: 'core' }), - jest.fn().mockReturnValue({ sync: 'injector' }), - jest.fn().mockResolvedValue({ is: 'merged-core' }), - ]; - - injectors.forEach((injector) => legacyInternals.injectUiAppVars('core', injector)); - - await expect(legacyInternals.getInjectedUiAppVars('core')).resolves.toMatchInlineSnapshot(` - Object { - "is": "merged-core", - "sync": "injector", - } - `); - expect(injectors[0]).toHaveBeenCalled(); - expect(injectors[1]).toHaveBeenCalled(); - expect(injectors[2]).toHaveBeenCalled(); - }); - }); - - describe('getVars()', () => { - let uiExports: LegacyUiExports; - let config: LegacyConfig; - let server: Server; - let legacyInternals: LegacyInternals; - - beforeEach(async () => { - uiExports = findLegacyPluginSpecsMock().uiExports; - config = configMock.create() as any; - server = httpServiceMock.createInternalSetupContract().server; - legacyInternals = new LegacyInternals(uiExports, config, server); - }); - - it('gets: no default injectors, no injected vars replacers, no ui app injectors, no inject arg', async () => { - const vars = await legacyInternals.getVars('core', httpServerMock.createRawRequest()); - - expect(vars).toMatchInlineSnapshot(`Object {}`); - }); - - it('gets: with default injectors, no injected vars replacers, no ui app injectors, no inject arg', async () => { - uiExports.defaultInjectedVarProviders = [ - varsProvider({ alpha: 'alpha' }), - varsProvider({ gamma: 'gamma' }), - varsProvider({ alpha: 'beta' }), - ]; - - const vars = await legacyInternals.getVars('core', httpServerMock.createRawRequest()); - - expect(vars).toMatchInlineSnapshot(` - Object { - "alpha": "beta", - "gamma": "gamma", - } - `); - }); - - it('gets: no default injectors, with injected vars replacers, with ui app injectors, no inject arg', async () => { - uiExports.injectedVarsReplacers = [ - jest.fn(async (vars) => ({ ...vars, added: 'key' })), - jest.fn((vars) => vars), - jest.fn((vars) => ({ replaced: 'all' })), - jest.fn(async (vars) => ({ ...vars, added: 'last-key' })), - ]; - - const request = httpServerMock.createRawRequest(); - const vars = await legacyInternals.getVars('core', request); - - expect(vars).toMatchInlineSnapshot(` - Object { - "added": "last-key", - "replaced": "all", - } - `); - }); - - it('gets: no default injectors, no injected vars replacers, with ui app injectors, no inject arg', async () => { - legacyInternals.injectUiAppVars('core', async () => ({ is: 'core' })); - legacyInternals.injectUiAppVars('core', () => ({ sync: 'injector' })); - legacyInternals.injectUiAppVars('core', async () => ({ is: 'merged-core' })); - - const vars = await legacyInternals.getVars('core', httpServerMock.createRawRequest()); - - expect(vars).toMatchInlineSnapshot(` - Object { - "is": "merged-core", - "sync": "injector", - } - `); - }); - - it('gets: no default injectors, no injected vars replacers, no ui app injectors, with inject arg', async () => { - const vars = await legacyInternals.getVars('core', httpServerMock.createRawRequest(), { - injected: 'arg', - }); - - expect(vars).toMatchInlineSnapshot(` - Object { - "injected": "arg", - } - `); - }); - - it('gets: with default injectors, with injected vars replacers, with ui app injectors, with inject arg', async () => { - uiExports.defaultInjectedVarProviders = [ - varsProvider({ alpha: 'alpha' }), - varsProvider({ gamma: 'gamma' }), - varsProvider({ alpha: 'beta' }), - ]; - uiExports.injectedVarsReplacers = [jest.fn(async (vars) => ({ ...vars, gamma: 'delta' }))]; - - legacyInternals.injectUiAppVars('core', async () => ({ is: 'core' })); - legacyInternals.injectUiAppVars('core', () => ({ sync: 'injector' })); - legacyInternals.injectUiAppVars('core', async () => ({ is: 'merged-core' })); - - const vars = await legacyInternals.getVars('core', httpServerMock.createRawRequest(), { - injected: 'arg', - sync: 'arg', - }); - - expect(vars).toMatchInlineSnapshot(` - Object { - "alpha": "beta", - "gamma": "delta", - "injected": "arg", - "is": "merged-core", - "sync": "arg", - } - `); - }); - }); -}); diff --git a/src/core/server/legacy/legacy_internals.ts b/src/core/server/legacy/legacy_internals.ts deleted file mode 100644 index 628ca4ed12f6b..0000000000000 --- a/src/core/server/legacy/legacy_internals.ts +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Server } from 'hapi'; - -import { KibanaRequest, LegacyRequest } from '../http'; -import { ensureRawRequest } from '../http/router'; -import { mergeVars } from './merge_vars'; -import { ILegacyInternals, LegacyVars, VarsInjector, LegacyConfig, LegacyUiExports } from './types'; - -/** - * @internal - * @deprecated - */ -export class LegacyInternals implements ILegacyInternals { - private readonly injectors = new Map>(); - private cachedDefaultVars?: LegacyVars; - - constructor( - private readonly uiExports: LegacyUiExports, - private readonly config: LegacyConfig, - private readonly server: Server - ) {} - - private get defaultVars(): LegacyVars { - if (this.cachedDefaultVars) { - return this.cachedDefaultVars; - } - - const { defaultInjectedVarProviders = [] } = this.uiExports; - - return (this.cachedDefaultVars = defaultInjectedVarProviders.reduce( - (vars, { fn, pluginSpec }) => - mergeVars(vars, fn(this.server, pluginSpec.readConfigValue(this.config, []))), - {} - )); - } - - private replaceVars(vars: LegacyVars, request: KibanaRequest | LegacyRequest) { - const { injectedVarsReplacers = [] } = this.uiExports; - - return injectedVarsReplacers.reduce( - async (injected, replacer) => - replacer(await injected, ensureRawRequest(request), this.server), - Promise.resolve(vars) - ); - } - - public injectUiAppVars(id: string, injector: VarsInjector) { - if (!this.injectors.has(id)) { - this.injectors.set(id, new Set()); - } - - this.injectors.get(id)!.add(injector); - } - - public getInjectedUiAppVars(id: string) { - return [...(this.injectors.get(id) || [])].reduce( - async (promise, injector) => ({ - ...(await promise), - ...(await injector()), - }), - Promise.resolve({}) - ); - } - - public async getVars( - id: string, - request: KibanaRequest | LegacyRequest, - injected: LegacyVars = {} - ) { - return this.replaceVars( - mergeVars(this.defaultVars, await this.getInjectedUiAppVars(id), injected), - request - ); - } -} diff --git a/src/core/server/legacy/legacy_service.mock.ts b/src/core/server/legacy/legacy_service.mock.ts index ab501bd6bb53b..781874f702cf8 100644 --- a/src/core/server/legacy/legacy_service.mock.ts +++ b/src/core/server/legacy/legacy_service.mock.ts @@ -18,26 +18,13 @@ */ import type { PublicMethodsOf } from '@kbn/utility-types'; import { LegacyService } from './legacy_service'; -import { LegacyConfig, LegacyServiceDiscoverPlugins, LegacyServiceSetupDeps } from './types'; +import { LegacyConfig, LegacyServiceSetupDeps } from './types'; type LegacyServiceMock = jest.Mocked & { legacyId: symbol }>; -const createDiscoverPluginsMock = (): LegacyServiceDiscoverPlugins => ({ - pluginSpecs: [], - uiExports: {}, - navLinks: [], - pluginExtendedConfig: { - get: jest.fn(), - has: jest.fn(), - set: jest.fn(), - }, - disabledPluginSpecs: [], - settings: {}, -}); - const createLegacyServiceMock = (): LegacyServiceMock => ({ legacyId: Symbol(), - discoverPlugins: jest.fn().mockResolvedValue(createDiscoverPluginsMock()), + setupLegacyConfig: jest.fn(), setup: jest.fn(), start: jest.fn(), stop: jest.fn(), @@ -52,6 +39,5 @@ const createLegacyConfigMock = (): jest.Mocked => ({ export const legacyServiceMock = { create: createLegacyServiceMock, createSetupContract: (deps: LegacyServiceSetupDeps) => createLegacyServiceMock().setup(deps), - createDiscoverPlugins: createDiscoverPluginsMock, createLegacyConfig: createLegacyConfigMock, }; diff --git a/src/core/server/legacy/legacy_service.test.mocks.ts b/src/core/server/legacy/legacy_service.test.mocks.ts deleted file mode 100644 index 9ad554d63add0..0000000000000 --- a/src/core/server/legacy/legacy_service.test.mocks.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { LegacyVars } from './types'; - -export const findLegacyPluginSpecsMock = jest.fn().mockImplementation((settings: LegacyVars) => ({ - pluginSpecs: [], - pluginExtendedConfig: { - has: jest.fn(), - get: jest.fn().mockReturnValue(settings), - set: jest.fn(), - }, - disabledPluginSpecs: [], - uiExports: {}, - navLinks: [], -})); -jest.doMock('./plugins/find_legacy_plugin_specs', () => ({ - findLegacyPluginSpecs: findLegacyPluginSpecsMock, -})); - -export const logLegacyThirdPartyPluginDeprecationWarningMock = jest.fn(); -jest.doMock('./plugins/log_legacy_plugins_warning', () => ({ - logLegacyThirdPartyPluginDeprecationWarning: logLegacyThirdPartyPluginDeprecationWarningMock, -})); diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index a6fe95deb3979..57009f0d35c16 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -19,10 +19,6 @@ jest.mock('../../../legacy/server/kbn_server'); jest.mock('./cluster_manager'); -import { - findLegacyPluginSpecsMock, - logLegacyThirdPartyPluginDeprecationWarningMock, -} from './legacy_service.test.mocks'; import { BehaviorSubject, throwError } from 'rxjs'; import { REPO_ROOT } from '@kbn/dev-utils'; @@ -44,8 +40,7 @@ import { capabilitiesServiceMock } from '../capabilities/capabilities_service.mo import { httpResourcesMock } from '../http_resources/http_resources_service.mock'; import { setupMock as renderingServiceMock } from '../rendering/__mocks__/rendering_service'; import { environmentServiceMock } from '../environment/environment_service.mock'; -import { findLegacyPluginSpecs } from './plugins'; -import { LegacyVars, LegacyServiceSetupDeps, LegacyServiceStartDeps } from './types'; +import { LegacyServiceSetupDeps, LegacyServiceStartDeps } from './types'; import { LegacyService } from './legacy_service'; import { coreMock } from '../mocks'; import { statusServiceMock } from '../status/status_service.mock'; @@ -73,7 +68,6 @@ beforeEach(() => { configService = configServiceMock.create(); environmentSetup = environmentServiceMock.createSetupContract(); - findLegacyPluginSpecsMock.mockClear(); MockKbnServer.prototype.ready = jest.fn().mockReturnValue(Promise.resolve()); MockKbnServer.prototype.listen = jest.fn(); @@ -149,10 +143,10 @@ describe('once LegacyService is set up with connection info', () => { coreId, env, logger, - configService: configService as any, + configService, }); - await legacyService.discoverPlugins(); + await legacyService.setupLegacyConfig(); await legacyService.setup(setupDeps); await legacyService.start(startDeps); @@ -160,13 +154,14 @@ describe('once LegacyService is set up with connection info', () => { expect(MockKbnServer).toHaveBeenCalledWith( { path: { autoListen: true }, server: { autoListen: true } }, // Because of the mock, path also gets the value expect.objectContaining({ get: expect.any(Function) }), - expect.any(Object), - { disabledPluginSpecs: [], pluginSpecs: [], uiExports: {}, navLinks: [] } + expect.any(Object) + ); + expect(MockKbnServer.mock.calls[0][1].get()).toEqual( + expect.objectContaining({ + path: expect.objectContaining({ autoListen: true }), + server: expect.objectContaining({ autoListen: true }), + }) ); - expect(MockKbnServer.mock.calls[0][1].get()).toEqual({ - path: { autoListen: true }, - server: { autoListen: true }, - }); const [mockKbnServer] = MockKbnServer.mock.instances; expect(mockKbnServer.listen).toHaveBeenCalledTimes(1); @@ -182,7 +177,7 @@ describe('once LegacyService is set up with connection info', () => { logger, configService: configService as any, }); - await legacyService.discoverPlugins(); + await legacyService.setupLegacyConfig(); await legacyService.setup(setupDeps); await legacyService.start(startDeps); @@ -190,13 +185,12 @@ describe('once LegacyService is set up with connection info', () => { expect(MockKbnServer).toHaveBeenCalledWith( { path: { autoListen: false }, server: { autoListen: true } }, expect.objectContaining({ get: expect.any(Function) }), - expect.any(Object), - { disabledPluginSpecs: [], pluginSpecs: [], uiExports: {}, navLinks: [] } + expect.any(Object) ); - expect(MockKbnServer.mock.calls[0][1].get()).toEqual({ - path: { autoListen: false }, - server: { autoListen: true }, - }); + + const legacyConfig = MockKbnServer.mock.calls[0][1].get(); + expect(legacyConfig.path.autoListen).toBe(false); + expect(legacyConfig.server.autoListen).toBe(true); const [mockKbnServer] = MockKbnServer.mock.instances; expect(mockKbnServer.ready).toHaveBeenCalledTimes(1); @@ -214,7 +208,7 @@ describe('once LegacyService is set up with connection info', () => { configService: configService as any, }); - await legacyService.discoverPlugins(); + await legacyService.setupLegacyConfig(); await legacyService.setup(setupDeps); await expect(legacyService.start(startDeps)).rejects.toThrowErrorMatchingInlineSnapshot( `"something failed"` @@ -234,11 +228,11 @@ describe('once LegacyService is set up with connection info', () => { configService: configService as any, }); - await expect(legacyService.discoverPlugins()).rejects.toThrowErrorMatchingInlineSnapshot( + await expect(legacyService.setupLegacyConfig()).rejects.toThrowErrorMatchingInlineSnapshot( `"something failed"` ); await expect(legacyService.setup(setupDeps)).rejects.toThrowErrorMatchingInlineSnapshot( - `"Legacy service has not discovered legacy plugins yet. Ensure LegacyService.discoverPlugins() is called before LegacyService.setup()"` + `"Legacy config not initialized yet. Ensure LegacyService.setupLegacyConfig() is called before LegacyService.setup()"` ); await expect(legacyService.start(startDeps)).rejects.toThrowErrorMatchingInlineSnapshot( `"Legacy service is not setup yet."` @@ -255,7 +249,7 @@ describe('once LegacyService is set up with connection info', () => { logger, configService: configService as any, }); - await legacyService.discoverPlugins(); + await legacyService.setupLegacyConfig(); await legacyService.setup(setupDeps); await legacyService.start(startDeps); @@ -276,7 +270,7 @@ describe('once LegacyService is set up with connection info', () => { logger, configService: configService as any, }); - await legacyService.discoverPlugins(); + await legacyService.setupLegacyConfig(); await legacyService.setup(setupDeps); await legacyService.start(startDeps); @@ -301,7 +295,7 @@ describe('once LegacyService is set up with connection info', () => { logger, configService: configService as any, }); - await legacyService.discoverPlugins(); + await legacyService.setupLegacyConfig(); await legacyService.setup(setupDeps); await legacyService.start(startDeps); @@ -321,7 +315,7 @@ describe('once LegacyService is set up without connection info', () => { let legacyService: LegacyService; beforeEach(async () => { legacyService = new LegacyService({ coreId, env, logger, configService: configService as any }); - await legacyService.discoverPlugins(); + await legacyService.setupLegacyConfig(); await legacyService.setup(setupDeps); await legacyService.start(startDeps); }); @@ -331,13 +325,13 @@ describe('once LegacyService is set up without connection info', () => { expect(MockKbnServer).toHaveBeenCalledWith( { path: {}, server: { autoListen: true } }, expect.objectContaining({ get: expect.any(Function) }), - expect.any(Object), - { disabledPluginSpecs: [], pluginSpecs: [], uiExports: {}, navLinks: [] } + expect.any(Object) + ); + expect(MockKbnServer.mock.calls[0][1].get()).toEqual( + expect.objectContaining({ + server: expect.objectContaining({ autoListen: true }), + }) ); - expect(MockKbnServer.mock.calls[0][1].get()).toEqual({ - path: {}, - server: { autoListen: true }, - }); }); test('reconfigures logging configuration if new config is received.', async () => { @@ -375,7 +369,7 @@ describe('once LegacyService is set up in `devClusterMaster` mode', () => { configService: configService as any, }); - await devClusterLegacyService.discoverPlugins(); + await devClusterLegacyService.setupLegacyConfig(); await devClusterLegacyService.setup(setupDeps); await devClusterLegacyService.start(startDeps); @@ -404,7 +398,7 @@ describe('once LegacyService is set up in `devClusterMaster` mode', () => { configService: configService as any, }); - await devClusterLegacyService.discoverPlugins(); + await devClusterLegacyService.setupLegacyConfig(); await devClusterLegacyService.setup(setupDeps); await devClusterLegacyService.start(startDeps); @@ -434,50 +428,6 @@ describe('start', () => { }); }); -describe('#discoverPlugins()', () => { - it('calls findLegacyPluginSpecs with correct parameters', async () => { - const legacyService = new LegacyService({ - coreId, - env, - logger, - configService: configService as any, - }); - - await legacyService.discoverPlugins(); - expect(findLegacyPluginSpecs).toHaveBeenCalledTimes(1); - expect(findLegacyPluginSpecs).toHaveBeenCalledWith(expect.any(Object), logger, env.packageInfo); - }); - - it(`logs deprecations for legacy third party plugins`, async () => { - const pluginSpecs = [{ getId: () => 'pluginA' }, { getId: () => 'pluginB' }]; - findLegacyPluginSpecsMock.mockImplementation( - (settings) => - Promise.resolve({ - pluginSpecs, - pluginExtendedConfig: settings, - disabledPluginSpecs: [], - uiExports: {}, - navLinks: [], - }) as any - ); - - const legacyService = new LegacyService({ - coreId, - env, - logger, - configService: configService as any, - }); - - await legacyService.discoverPlugins(); - - expect(logLegacyThirdPartyPluginDeprecationWarningMock).toHaveBeenCalledTimes(1); - expect(logLegacyThirdPartyPluginDeprecationWarningMock).toHaveBeenCalledWith({ - specs: pluginSpecs, - log: expect.any(Object), - }); - }); -}); - test('Sets the server.uuid property on the legacy configuration', async () => { configService.atPath.mockReturnValue(new BehaviorSubject({ autoListen: true })); const legacyService = new LegacyService({ @@ -489,23 +439,8 @@ test('Sets the server.uuid property on the legacy configuration', async () => { environmentSetup.instanceUuid = 'UUID_FROM_SERVICE'; - const configSetMock = jest.fn(); - - findLegacyPluginSpecsMock.mockImplementation((settings: LegacyVars) => ({ - pluginSpecs: [], - pluginExtendedConfig: { - has: jest.fn(), - get: jest.fn().mockReturnValue(settings), - set: configSetMock, - }, - disabledPluginSpecs: [], - uiExports: {}, - navLinks: [], - })); - - await legacyService.discoverPlugins(); + const { legacyConfig } = await legacyService.setupLegacyConfig(); await legacyService.setup(setupDeps); - expect(configSetMock).toHaveBeenCalledTimes(1); - expect(configSetMock).toHaveBeenCalledWith('server.uuid', 'UUID_FROM_SERVICE'); + expect(legacyConfig.get('server.uuid')).toBe('UUID_FROM_SERVICE'); }); diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 4dc22be2a9971..086e20c98c1a3 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -16,11 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -import type { PublicMethodsOf } from '@kbn/utility-types'; + import { combineLatest, ConnectableObservable, EMPTY, Observable, Subscription } from 'rxjs'; import { first, map, publishReplay, tap } from 'rxjs/operators'; - +import type { PublicMethodsOf } from '@kbn/utility-types'; import { PathConfigType } from '@kbn/utils'; + +// @ts-expect-error legacy config class +import { Config as LegacyConfigClass } from '../../../legacy/server/config'; import { CoreService } from '../../types'; import { Config } from '../config'; import { CoreContext } from '../core_context'; @@ -28,17 +31,7 @@ import { CspConfigType, config as cspConfig } from '../csp'; import { DevConfig, DevConfigType, config as devConfig } from '../dev'; import { BasePathProxyServer, HttpConfig, HttpConfigType, config as httpConfig } from '../http'; import { Logger } from '../logging'; -import { findLegacyPluginSpecs, logLegacyThirdPartyPluginDeprecationWarning } from './plugins'; -import { - ILegacyInternals, - LegacyServiceSetupDeps, - LegacyServiceStartDeps, - LegacyPlugins, - LegacyServiceDiscoverPlugins, - LegacyConfig, - LegacyVars, -} from './types'; -import { LegacyInternals } from './legacy_internals'; +import { LegacyServiceSetupDeps, LegacyServiceStartDeps, LegacyConfig, LegacyVars } from './types'; import { CoreSetup, CoreStart } from '..'; interface LegacyKbnServer { @@ -80,9 +73,7 @@ export class LegacyService implements CoreService { private setupDeps?: LegacyServiceSetupDeps; private update$?: ConnectableObservable<[Config, PathConfigType]>; private legacyRawConfig?: LegacyConfig; - private legacyPlugins?: LegacyPlugins; private settings?: LegacyVars; - public legacyInternals?: ILegacyInternals; constructor(private readonly coreContext: CoreContext) { const { logger, configService } = coreContext; @@ -97,11 +88,11 @@ export class LegacyService implements CoreService { ).pipe(map(([http, csp]) => new HttpConfig(http, csp))); } - public async discoverPlugins(): Promise { - this.update$ = combineLatest( + public async setupLegacyConfig() { + this.update$ = combineLatest([ this.coreContext.configService.getConfig$(), - this.coreContext.configService.atPath('path') - ).pipe( + this.coreContext.configService.atPath('path'), + ]).pipe( tap(([config, pathConfig]) => { if (this.kbnServer !== undefined) { this.kbnServer.applyLoggingConfiguration(getLegacyRawConfig(config, pathConfig)); @@ -120,74 +111,33 @@ export class LegacyService implements CoreService { ) .toPromise(); - const { - pluginSpecs, - pluginExtendedConfig, - disabledPluginSpecs, - uiExports, - navLinks, - } = await findLegacyPluginSpecs( - this.settings, - this.coreContext.logger, - this.coreContext.env.packageInfo - ); - - logLegacyThirdPartyPluginDeprecationWarning({ - specs: pluginSpecs, - log: this.log, - }); - - this.legacyPlugins = { - pluginSpecs, - disabledPluginSpecs, - uiExports, - navLinks, - }; - - this.legacyRawConfig = pluginExtendedConfig; - - // check for unknown uiExport types - if (uiExports.unknown && uiExports.unknown.length > 0) { - throw new Error( - `Unknown uiExport types: ${uiExports.unknown - .map(({ pluginSpec, type }) => `${type} from ${pluginSpec.getId()}`) - .join(', ')}` - ); - } + this.legacyRawConfig = LegacyConfigClass.withDefaultSchema(this.settings); return { - pluginSpecs, - disabledPluginSpecs, - uiExports, - navLinks, - pluginExtendedConfig, settings: this.settings, + legacyConfig: this.legacyRawConfig!, }; } public async setup(setupDeps: LegacyServiceSetupDeps) { this.log.debug('setting up legacy service'); - if (!this.legacyPlugins) { + if (!this.legacyRawConfig) { throw new Error( - 'Legacy service has not discovered legacy plugins yet. Ensure LegacyService.discoverPlugins() is called before LegacyService.setup()' + 'Legacy config not initialized yet. Ensure LegacyService.setupLegacyConfig() is called before LegacyService.setup()' ); } // propagate the instance uuid to the legacy config, as it was the legacy way to access it. this.legacyRawConfig!.set('server.uuid', setupDeps.core.environment.instanceUuid); + this.setupDeps = setupDeps; - this.legacyInternals = new LegacyInternals( - this.legacyPlugins.uiExports, - this.legacyRawConfig!, - setupDeps.core.http.server - ); } public async start(startDeps: LegacyServiceStartDeps) { const { setupDeps } = this; - if (!setupDeps || !this.legacyPlugins) { + if (!setupDeps || !this.legacyRawConfig) { throw new Error('Legacy service is not setup yet.'); } @@ -201,8 +151,7 @@ export class LegacyService implements CoreService { this.settings!, this.legacyRawConfig!, setupDeps, - startDeps, - this.legacyPlugins! + startDeps ); } } @@ -245,8 +194,7 @@ export class LegacyService implements CoreService { settings: LegacyVars, config: LegacyConfig, setupDeps: LegacyServiceSetupDeps, - startDeps: LegacyServiceStartDeps, - legacyPlugins: LegacyPlugins + startDeps: LegacyServiceStartDeps ) { const coreStart: CoreStart = { capabilities: startDeps.core.capabilities, @@ -337,36 +285,26 @@ export class LegacyService implements CoreService { // eslint-disable-next-line @typescript-eslint/no-var-requires const KbnServer = require('../../../legacy/server/kbn_server'); - const kbnServer: LegacyKbnServer = new KbnServer( - settings, - config, - { - env: { - mode: this.coreContext.env.mode, - packageInfo: this.coreContext.env.packageInfo, - }, - setupDeps: { - core: coreSetup, - plugins: setupDeps.plugins, - }, - startDeps: { - core: coreStart, - plugins: startDeps.plugins, - }, - __internals: { - http: { - registerStaticDir: setupDeps.core.http.registerStaticDir, - }, - hapiServer: setupDeps.core.http.server, - uiPlugins: setupDeps.uiPlugins, - elasticsearch: setupDeps.core.elasticsearch, - rendering: setupDeps.core.rendering, - legacy: this.legacyInternals, - }, - logger: this.coreContext.logger, + const kbnServer: LegacyKbnServer = new KbnServer(settings, config, { + env: { + mode: this.coreContext.env.mode, + packageInfo: this.coreContext.env.packageInfo, }, - legacyPlugins - ); + setupDeps: { + core: coreSetup, + plugins: setupDeps.plugins, + }, + startDeps: { + core: coreStart, + plugins: startDeps.plugins, + }, + __internals: { + hapiServer: setupDeps.core.http.server, + uiPlugins: setupDeps.uiPlugins, + rendering: setupDeps.core.rendering, + }, + logger: this.coreContext.logger, + }); // The kbnWorkerType check is necessary to prevent the repl // from being started multiple times in different processes. diff --git a/src/core/server/legacy/plugins/collect_ui_exports.js b/src/core/server/legacy/plugins/collect_ui_exports.js deleted file mode 100644 index 842ab554d79d1..0000000000000 --- a/src/core/server/legacy/plugins/collect_ui_exports.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { collectUiExports } from '../../../../legacy/ui/ui_exports/collect_ui_exports'; diff --git a/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts b/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts deleted file mode 100644 index cb4277b130a88..0000000000000 --- a/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Observable, merge, forkJoin } from 'rxjs'; -import { toArray, tap, distinct, map } from 'rxjs/operators'; - -import { - findPluginSpecs, - defaultConfig, - // @ts-expect-error -} from '../../../../legacy/plugin_discovery/find_plugin_specs.js'; -// @ts-expect-error -import { collectUiExports as collectLegacyUiExports } from './collect_ui_exports'; - -import { LoggerFactory } from '../../logging'; -import { PackageInfo } from '../../config'; -import { LegacyUiExports, LegacyPluginSpec, LegacyPluginPack, LegacyConfig } from '../types'; - -export async function findLegacyPluginSpecs( - settings: unknown, - loggerFactory: LoggerFactory, - packageInfo: PackageInfo -) { - const configToMutate: LegacyConfig = defaultConfig(settings); - const { - pack$, - invalidDirectoryError$, - invalidPackError$, - otherError$, - deprecation$, - invalidVersionSpec$, - spec$, - disabledSpec$, - }: { - pack$: Observable; - invalidDirectoryError$: Observable<{ path: string }>; - invalidPackError$: Observable<{ path: string }>; - otherError$: Observable; - deprecation$: Observable<{ spec: LegacyPluginSpec; message: string }>; - invalidVersionSpec$: Observable; - spec$: Observable; - disabledSpec$: Observable; - } = findPluginSpecs(settings, configToMutate) as any; - - const logger = loggerFactory.get('legacy-plugins'); - - const log$ = merge( - pack$.pipe( - tap((definition) => { - const path = definition.getPath(); - logger.debug(`Found plugin at ${path}`, { path }); - }) - ), - - invalidDirectoryError$.pipe( - tap((error) => { - logger.warn(`Unable to scan directory for plugins "${error.path}"`, { - err: error, - dir: error.path, - }); - }) - ), - - invalidPackError$.pipe( - tap((error) => { - logger.warn(`Skipping non-plugin directory at ${error.path}`, { - path: error.path, - }); - }) - ), - - otherError$.pipe( - tap((error) => { - // rethrow unhandled errors, which will fail the server - throw error; - }) - ), - - invalidVersionSpec$.pipe( - map((spec) => { - const name = spec.getId(); - const pluginVersion = spec.getExpectedKibanaVersion(); - const kibanaVersion = packageInfo.version; - return `Plugin "${name}" was disabled because it expected Kibana version "${pluginVersion}", and found "${kibanaVersion}".`; - }), - distinct(), - tap((message) => { - logger.warn(message); - }) - ), - - deprecation$.pipe( - tap(({ spec, message }) => { - const deprecationLogger = loggerFactory.get( - 'plugins', - spec.getConfigPrefix(), - 'config', - 'deprecation' - ); - deprecationLogger.warn(message); - }) - ) - ); - - const [disabledPluginSpecs, pluginSpecs] = await forkJoin( - disabledSpec$.pipe(toArray()), - spec$.pipe(toArray()), - log$.pipe(toArray()) - ).toPromise(); - const uiExports: LegacyUiExports = collectLegacyUiExports(pluginSpecs); - - return { - disabledPluginSpecs, - pluginSpecs, - pluginExtendedConfig: configToMutate, - uiExports, - navLinks: [], - }; -} diff --git a/src/core/server/legacy/plugins/index.ts b/src/core/server/legacy/plugins/index.ts deleted file mode 100644 index 7ec5dbc1983ab..0000000000000 --- a/src/core/server/legacy/plugins/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { findLegacyPluginSpecs } from './find_legacy_plugin_specs'; -export { logLegacyThirdPartyPluginDeprecationWarning } from './log_legacy_plugins_warning'; diff --git a/src/core/server/legacy/plugins/log_legacy_plugins_warning.test.ts b/src/core/server/legacy/plugins/log_legacy_plugins_warning.test.ts deleted file mode 100644 index 2317f1036ce42..0000000000000 --- a/src/core/server/legacy/plugins/log_legacy_plugins_warning.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { loggerMock } from '../../logging/logger.mock'; -import { logLegacyThirdPartyPluginDeprecationWarning } from './log_legacy_plugins_warning'; -import { LegacyPluginSpec } from '../types'; - -const createPluginSpec = ({ id, path }: { id: string; path: string }): LegacyPluginSpec => { - return { - getId: () => id, - getExpectedKibanaVersion: () => 'kibana', - getConfigPrefix: () => 'plugin.config', - getPack: () => ({ - getPath: () => path, - }), - }; -}; - -describe('logLegacyThirdPartyPluginDeprecationWarning', () => { - let log: ReturnType; - - beforeEach(() => { - log = loggerMock.create(); - }); - - it('logs warning for third party plugins', () => { - logLegacyThirdPartyPluginDeprecationWarning({ - specs: [createPluginSpec({ id: 'plugin', path: '/some-external-path' })], - log, - }); - expect(log.warn).toHaveBeenCalledTimes(1); - expect(log.warn.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "Some installed third party plugin(s) [plugin] are using the legacy plugin format and will no longer work in a future Kibana release. Please refer to https://ela.st/kibana-breaking-changes-8-0 for a list of breaking changes and https://ela.st/kibana-platform-migration for documentation on how to migrate legacy plugins.", - ] - `); - }); - - it('lists all the deprecated plugins and only log once', () => { - logLegacyThirdPartyPluginDeprecationWarning({ - specs: [ - createPluginSpec({ id: 'pluginA', path: '/abs/path/to/pluginA' }), - createPluginSpec({ id: 'pluginB', path: '/abs/path/to/pluginB' }), - createPluginSpec({ id: 'pluginC', path: '/abs/path/to/pluginC' }), - ], - log, - }); - expect(log.warn).toHaveBeenCalledTimes(1); - expect(log.warn.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "Some installed third party plugin(s) [pluginA, pluginB, pluginC] are using the legacy plugin format and will no longer work in a future Kibana release. Please refer to https://ela.st/kibana-breaking-changes-8-0 for a list of breaking changes and https://ela.st/kibana-platform-migration for documentation on how to migrate legacy plugins.", - ] - `); - }); - - it('does not log warning for internal legacy plugins', () => { - logLegacyThirdPartyPluginDeprecationWarning({ - specs: [ - createPluginSpec({ - id: 'plugin', - path: '/absolute/path/to/kibana/src/legacy/core_plugins', - }), - createPluginSpec({ - id: 'plugin', - path: '/absolute/path/to/kibana/x-pack', - }), - ], - log, - }); - - expect(log.warn).not.toHaveBeenCalled(); - }); -}); diff --git a/src/core/server/legacy/plugins/log_legacy_plugins_warning.ts b/src/core/server/legacy/plugins/log_legacy_plugins_warning.ts deleted file mode 100644 index 4a4a1b1b0e60b..0000000000000 --- a/src/core/server/legacy/plugins/log_legacy_plugins_warning.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Logger } from '../../logging'; -import { LegacyPluginSpec } from '../types'; - -const internalPaths = ['/src/legacy/core_plugins', '/x-pack']; - -// Use shortened URLs so destinations can be updated if/when documentation moves -// All platform team members have access to edit these -const breakingChangesUrl = 'https://ela.st/kibana-breaking-changes-8-0'; -const migrationGuideUrl = 'https://ela.st/kibana-platform-migration'; - -export const logLegacyThirdPartyPluginDeprecationWarning = ({ - specs, - log, -}: { - specs: LegacyPluginSpec[]; - log: Logger; -}) => { - const thirdPartySpecs = specs.filter(isThirdPartyPluginSpec); - if (thirdPartySpecs.length > 0) { - const pluginIds = thirdPartySpecs.map((spec) => spec.getId()); - log.warn( - `Some installed third party plugin(s) [${pluginIds.join( - ', ' - )}] are using the legacy plugin format and will no longer work in a future Kibana release. ` + - `Please refer to ${breakingChangesUrl} for a list of breaking changes ` + - `and ${migrationGuideUrl} for documentation on how to migrate legacy plugins.` - ); - } -}; - -const isThirdPartyPluginSpec = (spec: LegacyPluginSpec): boolean => { - const pluginPath = spec.getPack().getPath(); - return !internalPaths.some((internalPath) => pluginPath.indexOf(internalPath) > -1); -}; diff --git a/src/core/server/legacy/types.ts b/src/core/server/legacy/types.ts index 1105308fd44cf..12bfddfff1961 100644 --- a/src/core/server/legacy/types.ts +++ b/src/core/server/legacy/types.ts @@ -17,10 +17,6 @@ * under the License. */ -import { Server } from 'hapi'; - -import { ChromeNavLink } from '../../public'; -import { KibanaRequest, LegacyRequest } from '../http'; import { InternalCoreSetup, InternalCoreStart } from '../internal_types'; import { PluginsServiceSetup, PluginsServiceStart, UiPlugins } from '../plugins'; import { InternalRenderingServiceSetup } from '../rendering'; @@ -50,91 +46,6 @@ export interface LegacyConfig { set(config: LegacyVars): void; } -/** - * @internal - * @deprecated - */ -export interface LegacyPluginPack { - getPath(): string; -} - -/** - * @internal - * @deprecated - */ -export interface LegacyPluginSpec { - getId: () => unknown; - getExpectedKibanaVersion: () => string; - getConfigPrefix: () => string; - getPack: () => LegacyPluginPack; -} - -/** - * @internal - * @deprecated - */ -export interface VarsProvider { - fn: (server: Server, configValue: any) => LegacyVars; - pluginSpec: { - readConfigValue(config: any, key: string | string[]): any; - }; -} - -/** - * @internal - * @deprecated - */ -export type VarsInjector = () => LegacyVars; - -/** - * @internal - * @deprecated - */ -export type VarsReplacer = ( - vars: LegacyVars, - request: LegacyRequest, - server: Server -) => LegacyVars | Promise; - -/** - * @internal - * @deprecated - */ -export type LegacyNavLinkSpec = Partial & { - id: string; - title: string; - url: string; -}; - -/** - * @internal - * @deprecated - */ -export type LegacyAppSpec = Partial & { - pluginId?: string; - listed?: boolean; -}; - -/** - * @internal - * @deprecated - */ -export type LegacyNavLink = Omit & { - order: number; -}; - -/** - * @internal - * @deprecated - */ -export interface LegacyUiExports { - defaultInjectedVarProviders?: VarsProvider[]; - injectedVarsReplacers?: VarsReplacer[]; - navLinkSpecs?: LegacyNavLinkSpec[] | null; - uiAppSpecs?: Array; - unknown?: [{ pluginSpec: LegacyPluginSpec; type: unknown }]; -} - /** * @public * @deprecated @@ -158,43 +69,7 @@ export interface LegacyServiceStartDeps { * @internal * @deprecated */ -export interface ILegacyInternals { - /** - * Inject UI app vars for a particular plugin - */ - injectUiAppVars(id: string, injector: VarsInjector): void; - - /** - * Get all the merged injected UI app vars for a particular plugin - */ - getInjectedUiAppVars(id: string): Promise; - - /** - * Get the metadata vars for a particular plugin - */ - getVars( - id: string, - request: KibanaRequest | LegacyRequest, - injected?: LegacyVars - ): Promise; -} - -/** - * @internal - * @deprecated - */ -export interface LegacyPlugins { - disabledPluginSpecs: LegacyPluginSpec[]; - pluginSpecs: LegacyPluginSpec[]; - uiExports: LegacyUiExports; - navLinks: LegacyNavLink[]; -} - -/** - * @internal - * @deprecated - */ -export interface LegacyServiceDiscoverPlugins extends LegacyPlugins { - pluginExtendedConfig: LegacyConfig; +export interface LegacyServiceSetupConfig { + legacyConfig: LegacyConfig; settings: LegacyVars; } diff --git a/src/core/server/rendering/__mocks__/params.ts b/src/core/server/rendering/__mocks__/params.ts index 0901cec768cd2..ae3830f703a53 100644 --- a/src/core/server/rendering/__mocks__/params.ts +++ b/src/core/server/rendering/__mocks__/params.ts @@ -20,19 +20,16 @@ import { mockCoreContext } from '../../core_context.mock'; import { httpServiceMock } from '../../http/http_service.mock'; import { pluginServiceMock } from '../../plugins/plugins_service.mock'; -import { legacyServiceMock } from '../../legacy/legacy_service.mock'; import { statusServiceMock } from '../../status/status_service.mock'; const context = mockCoreContext.create(); const http = httpServiceMock.createInternalSetupContract(); const uiPlugins = pluginServiceMock.createUiPlugins(); -const legacyPlugins = legacyServiceMock.createDiscoverPlugins(); const status = statusServiceMock.createInternalSetupContract(); export const mockRenderingServiceParams = context; export const mockRenderingSetupDeps = { http, - legacyPlugins, uiPlugins, status, }; diff --git a/src/core/server/rendering/__mocks__/rendering_service.ts b/src/core/server/rendering/__mocks__/rendering_service.ts index 179a09b8619b0..01d084f9ae53c 100644 --- a/src/core/server/rendering/__mocks__/rendering_service.ts +++ b/src/core/server/rendering/__mocks__/rendering_service.ts @@ -27,11 +27,9 @@ export const setupMock: jest.Mocked = { render: jest.fn(), }; export const mockSetup = jest.fn().mockResolvedValue(setupMock); -export const mockStart = jest.fn(); export const mockStop = jest.fn(); export const mockRenderingService: jest.Mocked = { setup: mockSetup, - start: mockStart, stop: mockStop, }; export const RenderingService = jest.fn( diff --git a/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap b/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap index ab828a1780425..07ca59a48c6b0 100644 --- a/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap +++ b/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap @@ -27,15 +27,6 @@ Object { "translationsUrl": "/mock-server-basepath/translations/en.json", }, "legacyMetadata": Object { - "app": Object {}, - "basePath": "/mock-server-basepath", - "branch": Any, - "buildNum": Any, - "buildSha": Any, - "bundleId": "app:core", - "devMode": true, - "nav": Array [], - "serverName": "http-server-test", "uiSettings": Object { "defaults": Object { "registered": Object { @@ -44,7 +35,6 @@ Object { }, "user": Object {}, }, - "version": Any, }, "serverBasePath": "/mock-server-basepath", "uiPlugins": Array [], @@ -80,15 +70,6 @@ Object { "translationsUrl": "/mock-server-basepath/translations/en.json", }, "legacyMetadata": Object { - "app": Object {}, - "basePath": "/mock-server-basepath", - "branch": Any, - "buildNum": Any, - "buildSha": Any, - "bundleId": "app:core", - "devMode": true, - "nav": Array [], - "serverName": "http-server-test", "uiSettings": Object { "defaults": Object { "registered": Object { @@ -97,7 +78,6 @@ Object { }, "user": Object {}, }, - "version": Any, }, "serverBasePath": "/mock-server-basepath", "uiPlugins": Array [], @@ -133,15 +113,6 @@ Object { "translationsUrl": "/mock-server-basepath/translations/en.json", }, "legacyMetadata": Object { - "app": Object {}, - "basePath": "/mock-server-basepath", - "branch": Any, - "buildNum": Any, - "buildSha": Any, - "bundleId": "app:core", - "devMode": true, - "nav": Array [], - "serverName": "http-server-test", "uiSettings": Object { "defaults": Object { "registered": Object { @@ -154,7 +125,6 @@ Object { }, }, }, - "version": Any, }, "serverBasePath": "/mock-server-basepath", "uiPlugins": Array [], @@ -190,15 +160,6 @@ Object { "translationsUrl": "/translations/en.json", }, "legacyMetadata": Object { - "app": Object {}, - "basePath": "", - "branch": Any, - "buildNum": Any, - "buildSha": Any, - "bundleId": "app:core", - "devMode": true, - "nav": Array [], - "serverName": "http-server-test", "uiSettings": Object { "defaults": Object { "registered": Object { @@ -207,7 +168,6 @@ Object { }, "user": Object {}, }, - "version": Any, }, "serverBasePath": "/mock-server-basepath", "uiPlugins": Array [], @@ -243,15 +203,6 @@ Object { "translationsUrl": "/mock-server-basepath/translations/en.json", }, "legacyMetadata": Object { - "app": Object {}, - "basePath": "/mock-server-basepath", - "branch": Any, - "buildNum": Any, - "buildSha": Any, - "bundleId": "app:core", - "devMode": true, - "nav": Array [], - "serverName": "http-server-test", "uiSettings": Object { "defaults": Object { "registered": Object { @@ -260,7 +211,6 @@ Object { }, "user": Object {}, }, - "version": Any, }, "serverBasePath": "/mock-server-basepath", "uiPlugins": Array [], diff --git a/src/core/server/rendering/rendering_service.test.ts b/src/core/server/rendering/rendering_service.test.ts index 254bafed5b194..08978cd1df64d 100644 --- a/src/core/server/rendering/rendering_service.test.ts +++ b/src/core/server/rendering/rendering_service.test.ts @@ -43,12 +43,6 @@ const INJECTED_METADATA = { version: expect.any(String), }, }, - legacyMetadata: { - branch: expect.any(String), - buildNum: expect.any(Number), - buildSha: expect.any(String), - version: expect.any(String), - }, }; const { createKibanaRequest, createRawRequest } = httpServerMock; @@ -72,13 +66,6 @@ describe('RenderingService', () => { registered: { name: 'title' }, }); render = (await service.setup(mockRenderingSetupDeps)).render; - await service.start({ - legacy: { - legacyInternals: { - getVars: () => ({}), - }, - }, - } as any); }); it('renders "core" page', async () => { diff --git a/src/core/server/rendering/rendering_service.tsx b/src/core/server/rendering/rendering_service.tsx index 7761c89044f6f..738787f940905 100644 --- a/src/core/server/rendering/rendering_service.tsx +++ b/src/core/server/rendering/rendering_service.tsx @@ -20,14 +20,11 @@ import React from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; import { take } from 'rxjs/operators'; - import { i18n } from '@kbn/i18n'; import { UiPlugins } from '../plugins'; -import { CoreService } from '../../types'; import { CoreContext } from '../core_context'; import { Template } from './views'; -import { LegacyService } from '../legacy'; import { IRenderOptions, RenderingSetupDeps, @@ -36,25 +33,20 @@ import { } from './types'; /** @internal */ -export class RenderingService implements CoreService { - private legacyInternals?: LegacyService['legacyInternals']; +export class RenderingService { constructor(private readonly coreContext: CoreContext) {} public async setup({ http, status, - legacyPlugins, uiPlugins, }: RenderingSetupDeps): Promise { return { render: async ( request, uiSettings, - { app = { getId: () => 'core' }, includeUserSettings = true, vars }: IRenderOptions = {} + { includeUserSettings = true, vars }: IRenderOptions = {} ) => { - if (!this.legacyInternals) { - throw new Error('Cannot render before "start"'); - } const env = { mode: this.coreContext.env.mode, packageInfo: this.coreContext.env.packageInfo, @@ -65,7 +57,6 @@ export class RenderingService implements CoreService ({ id, @@ -96,16 +87,6 @@ export class RenderingService implements CoreService; }>; legacyMetadata: { - app: { getId(): string }; - bundleId: string; - nav: LegacyNavLink[]; - version: string; - branch: string; - buildNum: number; - buildSha: string; - serverName: string; - devMode: boolean; - basePath: string; uiSettings: { defaults: Record; user: Record>; @@ -78,7 +67,6 @@ export interface RenderingMetadata { /** @internal */ export interface RenderingSetupDeps { http: InternalHttpServiceSetup; - legacyPlugins: LegacyServiceDiscoverPlugins; status: InternalStatusServiceSetup; uiPlugins: UiPlugins; } @@ -91,14 +79,6 @@ export interface IRenderOptions { */ includeUserSettings?: boolean; - /** - * Render the bootstrapped HTML content for an optional legacy application. - * Defaults to `core`. - * @deprecated for legacy use only, remove with ui_render_mixin - * @internal - */ - app?: { getId(): string }; - /** * Inject custom vars into the page metadata. * @deprecated for legacy use only, remove with ui_render_mixin diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js index 352ce4c1c16eb..0e72ad2fec06c 100644 --- a/src/core/server/saved_objects/service/lib/repository.test.js +++ b/src/core/server/saved_objects/service/lib/repository.test.js @@ -2477,6 +2477,33 @@ describe('SavedObjectsRepository', () => { expect(client.search).not.toHaveBeenCalled(); }); + it(`throws when namespaces is an empty array`, async () => { + await expect( + savedObjectsRepository.find({ type: 'foo', namespaces: [] }) + ).rejects.toThrowError('options.namespaces cannot be an empty array'); + expect(client.search).not.toHaveBeenCalled(); + }); + + it(`throws when type is not falsy and typeToNamespacesMap is defined`, async () => { + await expect( + savedObjectsRepository.find({ type: 'foo', typeToNamespacesMap: new Map() }) + ).rejects.toThrowError( + 'options.type must be an empty string when options.typeToNamespacesMap is used' + ); + expect(client.search).not.toHaveBeenCalled(); + }); + + it(`throws when type is not an empty array and typeToNamespacesMap is defined`, async () => { + const test = async (args) => { + await expect(savedObjectsRepository.find(args)).rejects.toThrowError( + 'options.namespaces must be an empty array when options.typeToNamespacesMap is used' + ); + expect(client.search).not.toHaveBeenCalled(); + }; + await test({ type: '', typeToNamespacesMap: new Map() }); + await test({ type: '', namespaces: ['some-ns'], typeToNamespacesMap: new Map() }); + }); + it(`throws when searchFields is defined but not an array`, async () => { await expect( savedObjectsRepository.find({ type, searchFields: 'string' }) @@ -2493,7 +2520,7 @@ describe('SavedObjectsRepository', () => { it(`throws when KQL filter syntax is invalid`, async () => { const findOpts = { - namespace, + namespaces: [namespace], search: 'foo*', searchFields: ['foo'], type: ['dashboard'], @@ -2577,38 +2604,70 @@ describe('SavedObjectsRepository', () => { const test = async (types) => { const result = await savedObjectsRepository.find({ type: types }); expect(result).toEqual(expect.objectContaining({ saved_objects: [] })); + expect(client.search).not.toHaveBeenCalled(); }; await test('unknownType'); await test(HIDDEN_TYPE); await test(['unknownType', HIDDEN_TYPE]); }); + + it(`should return empty results when attempting to find only invalid or hidden types using typeToNamespacesMap`, async () => { + const test = async (types) => { + const result = await savedObjectsRepository.find({ + typeToNamespacesMap: new Map(types.map((x) => [x, undefined])), + type: '', + namespaces: [], + }); + expect(result).toEqual(expect.objectContaining({ saved_objects: [] })); + expect(client.search).not.toHaveBeenCalled(); + }; + + await test(['unknownType']); + await test([HIDDEN_TYPE]); + await test(['unknownType', HIDDEN_TYPE]); + }); }); describe('search dsl', () => { - it(`passes mappings, registry, search, defaultSearchOperator, searchFields, type, sortField, sortOrder and hasReference to getSearchDsl`, async () => { + const commonOptions = { + type: [type], // cannot be used when `typeToNamespacesMap` is present + namespaces: [namespace], // cannot be used when `typeToNamespacesMap` is present + search: 'foo*', + searchFields: ['foo'], + sortField: 'name', + sortOrder: 'desc', + defaultSearchOperator: 'AND', + hasReference: { + type: 'foo', + id: '1', + }, + kueryNode: undefined, + }; + + it(`passes mappings, registry, and search options to getSearchDsl`, async () => { + await findSuccess(commonOptions, namespace); + expect(getSearchDslNS.getSearchDsl).toHaveBeenCalledWith(mappings, registry, commonOptions); + }); + + it(`accepts typeToNamespacesMap`, async () => { const relevantOpts = { - namespaces: [namespace], - search: 'foo*', - searchFields: ['foo'], - type: [type], - sortField: 'name', - sortOrder: 'desc', - defaultSearchOperator: 'AND', - hasReference: { - type: 'foo', - id: '1', - }, - kueryNode: undefined, + ...commonOptions, + type: '', + namespaces: [], + typeToNamespacesMap: new Map([[type, [namespace]]]), // can only be used when `type` is falsy and `namespaces` is an empty array }; await findSuccess(relevantOpts, namespace); - expect(getSearchDslNS.getSearchDsl).toHaveBeenCalledWith(mappings, registry, relevantOpts); + expect(getSearchDslNS.getSearchDsl).toHaveBeenCalledWith(mappings, registry, { + ...relevantOpts, + type: [type], + }); }); it(`accepts KQL expression filter and passes KueryNode to getSearchDsl`, async () => { const findOpts = { - namespace, + namespaces: [namespace], search: 'foo*', searchFields: ['foo'], type: ['dashboard'], @@ -2649,7 +2708,7 @@ describe('SavedObjectsRepository', () => { it(`accepts KQL KueryNode filter and passes KueryNode to getSearchDsl`, async () => { const findOpts = { - namespace, + namespaces: [namespace], search: 'foo*', searchFields: ['foo'], type: ['dashboard'], diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index 125f97e7feb11..a83c86e585628 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -67,7 +67,7 @@ import { } from '../../types'; import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry'; import { validateConvertFilterToKueryNode } from './filter_utils'; -import { SavedObjectsUtils } from './utils'; +import { FIND_DEFAULT_PAGE, FIND_DEFAULT_PER_PAGE, SavedObjectsUtils } from './utils'; // BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository // so any breaking changes to this repository are considered breaking changes to the SavedObjectsClient. @@ -693,37 +693,51 @@ export class SavedObjectsRepository { * @property {string} [options.preference] * @returns {promise} - { saved_objects: [{ id, type, version, attributes }], total, per_page, page } */ - async find({ - search, - defaultSearchOperator = 'OR', - searchFields, - rootSearchFields, - hasReference, - page = 1, - perPage = 20, - sortField, - sortOrder, - fields, - namespaces, - type, - filter, - preference, - }: SavedObjectsFindOptions): Promise> { - if (!type) { + async find(options: SavedObjectsFindOptions): Promise> { + const { + search, + defaultSearchOperator = 'OR', + searchFields, + rootSearchFields, + hasReference, + page = FIND_DEFAULT_PAGE, + perPage = FIND_DEFAULT_PER_PAGE, + sortField, + sortOrder, + fields, + namespaces, + type, + typeToNamespacesMap, + filter, + preference, + } = options; + + if (!type && !typeToNamespacesMap) { throw SavedObjectsErrorHelpers.createBadRequestError( 'options.type must be a string or an array of strings' ); + } else if (namespaces?.length === 0 && !typeToNamespacesMap) { + throw SavedObjectsErrorHelpers.createBadRequestError( + 'options.namespaces cannot be an empty array' + ); + } else if (type && typeToNamespacesMap) { + throw SavedObjectsErrorHelpers.createBadRequestError( + 'options.type must be an empty string when options.typeToNamespacesMap is used' + ); + } else if ((!namespaces || namespaces?.length) && typeToNamespacesMap) { + throw SavedObjectsErrorHelpers.createBadRequestError( + 'options.namespaces must be an empty array when options.typeToNamespacesMap is used' + ); } - const types = Array.isArray(type) ? type : [type]; + const types = type + ? Array.isArray(type) + ? type + : [type] + : Array.from(typeToNamespacesMap!.keys()); const allowedTypes = types.filter((t) => this._allowedTypes.includes(t)); if (allowedTypes.length === 0) { - return { - page, - per_page: perPage, - total: 0, - saved_objects: [], - }; + return SavedObjectsUtils.createEmptyFindResponse(options); } if (searchFields && !Array.isArray(searchFields)) { @@ -766,6 +780,7 @@ export class SavedObjectsRepository { sortField, sortOrder, namespaces, + typeToNamespacesMap, hasReference, kueryNode, }), diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts index 4adc92df31805..e13c67a720400 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts @@ -50,6 +50,40 @@ const ALL_TYPE_SUBSETS = ALL_TYPES.reduce( .filter((x) => x.length) // exclude empty set .map((x) => (x.length === 1 ? x[0] : x)); // if a subset is a single string, destructure it +const createTypeClause = (type: string, namespaces?: string[]) => { + if (registry.isMultiNamespace(type)) { + return { + bool: { + must: expect.arrayContaining([{ terms: { namespaces: namespaces ?? ['default'] } }]), + must_not: [{ exists: { field: 'namespace' } }], + }, + }; + } else if (registry.isSingleNamespace(type)) { + const nonDefaultNamespaces = namespaces?.filter((n) => n !== 'default') ?? []; + const should: any = []; + if (nonDefaultNamespaces.length > 0) { + should.push({ terms: { namespace: nonDefaultNamespaces } }); + } + if (namespaces?.includes('default')) { + should.push({ bool: { must_not: [{ exists: { field: 'namespace' } }] } }); + } + return { + bool: { + must: [{ term: { type } }], + should: expect.arrayContaining(should), + minimum_should_match: 1, + must_not: [{ exists: { field: 'namespaces' } }], + }, + }; + } + // isNamespaceAgnostic + return { + bool: expect.objectContaining({ + must_not: [{ exists: { field: 'namespace' } }, { exists: { field: 'namespaces' } }], + }), + }; +}; + /** * Note: these tests cases are defined in the order they appear in the source code, for readability's sake */ @@ -198,40 +232,6 @@ describe('#getQueryParams', () => { }); describe('`namespaces` parameter', () => { - const createTypeClause = (type: string, namespaces?: string[]) => { - if (registry.isMultiNamespace(type)) { - return { - bool: { - must: expect.arrayContaining([{ terms: { namespaces: namespaces ?? ['default'] } }]), - must_not: [{ exists: { field: 'namespace' } }], - }, - }; - } else if (registry.isSingleNamespace(type)) { - const nonDefaultNamespaces = namespaces?.filter((n) => n !== 'default') ?? []; - const should: any = []; - if (nonDefaultNamespaces.length > 0) { - should.push({ terms: { namespace: nonDefaultNamespaces } }); - } - if (namespaces?.includes('default')) { - should.push({ bool: { must_not: [{ exists: { field: 'namespace' } }] } }); - } - return { - bool: { - must: [{ term: { type } }], - should: expect.arrayContaining(should), - minimum_should_match: 1, - must_not: [{ exists: { field: 'namespaces' } }], - }, - }; - } - // isNamespaceAgnostic - return { - bool: expect.objectContaining({ - must_not: [{ exists: { field: 'namespace' } }, { exists: { field: 'namespaces' } }], - }), - }; - }; - const expectResult = (result: Result, ...typeClauses: any) => { expect(result.query.bool.filter).toEqual( expect.arrayContaining([ @@ -281,6 +281,37 @@ describe('#getQueryParams', () => { test(['default']); }); }); + + describe('`typeToNamespacesMap` parameter', () => { + const expectResult = (result: Result, ...typeClauses: any) => { + expect(result.query.bool.filter).toEqual( + expect.arrayContaining([ + { bool: expect.objectContaining({ should: typeClauses, minimum_should_match: 1 }) }, + ]) + ); + }; + + it('supersedes `type` and `namespaces` parameters', () => { + const result = getQueryParams({ + mappings, + registry, + type: ['pending', 'saved', 'shared', 'global'], + namespaces: ['foo', 'bar', 'default'], + typeToNamespacesMap: new Map([ + ['pending', ['foo']], // 'pending' is only authorized in the 'foo' namespace + // 'saved' is not authorized in any namespaces + ['shared', ['bar', 'default']], // 'shared' is only authorized in the 'bar' and 'default' namespaces + ['global', ['foo', 'bar', 'default']], // 'global' is authorized in all namespaces (which are ignored anyway) + ]), + }); + expectResult( + result, + createTypeClause('pending', ['foo']), + createTypeClause('shared', ['bar', 'default']), + createTypeClause('global') + ); + }); + }); }); describe('search clause (query.bool.must.simple_query_string)', () => { diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts index 642d51c70766e..eaddc05fa921c 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts @@ -129,6 +129,7 @@ interface QueryParams { registry: ISavedObjectTypeRegistry; namespaces?: string[]; type?: string | string[]; + typeToNamespacesMap?: Map; search?: string; searchFields?: string[]; rootSearchFields?: string[]; @@ -145,6 +146,7 @@ export function getQueryParams({ registry, namespaces, type, + typeToNamespacesMap, search, searchFields, rootSearchFields, @@ -152,7 +154,10 @@ export function getQueryParams({ hasReference, kueryNode, }: QueryParams) { - const types = getTypes(mappings, type); + const types = getTypes( + mappings, + typeToNamespacesMap ? Array.from(typeToNamespacesMap.keys()) : type + ); // A de-duplicated set of namespaces makes for a more effecient query. // @@ -163,9 +168,12 @@ export function getQueryParams({ // since that is consistent with how a single-namespace search behaves in the OSS distribution. Leaving the wildcard in place // would result in no results being returned, as the wildcard is treated as a literal, and not _actually_ as a wildcard. // We had a good discussion around the tradeoffs here: https://github.com/elastic/kibana/pull/67644#discussion_r441055716 - const normalizedNamespaces = namespaces - ? Array.from(new Set(namespaces.map((x) => (x === '*' ? DEFAULT_NAMESPACE_STRING : x)))) - : undefined; + const normalizeNamespaces = (namespacesToNormalize?: string[]) => + namespacesToNormalize + ? Array.from( + new Set(namespacesToNormalize.map((x) => (x === '*' ? DEFAULT_NAMESPACE_STRING : x))) + ) + : undefined; const bool: any = { filter: [ @@ -197,9 +205,12 @@ export function getQueryParams({ }, ] : undefined, - should: types.map((shouldType) => - getClauseForType(registry, normalizedNamespaces, shouldType) - ), + should: types.map((shouldType) => { + const normalizedNamespaces = normalizeNamespaces( + typeToNamespacesMap ? typeToNamespacesMap.get(shouldType) : namespaces + ); + return getClauseForType(registry, normalizedNamespaces, shouldType); + }), minimum_should_match: 1, }, }, diff --git a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts index 62e629ad33cc8..7276e505bce7d 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts @@ -57,10 +57,11 @@ describe('getSearchDsl', () => { }); describe('passes control', () => { - it('passes (mappings, schema, namespaces, type, search, searchFields, rootSearchFields, hasReference) to getQueryParams', () => { + it('passes (mappings, schema, namespaces, type, typeToNamespacesMap, search, searchFields, rootSearchFields, hasReference) to getQueryParams', () => { const opts = { namespaces: ['foo-namespace'], type: 'foo', + typeToNamespacesMap: new Map(), search: 'bar', searchFields: ['baz'], rootSearchFields: ['qux'], @@ -78,6 +79,7 @@ describe('getSearchDsl', () => { registry, namespaces: opts.namespaces, type: opts.type, + typeToNamespacesMap: opts.typeToNamespacesMap, search: opts.search, searchFields: opts.searchFields, rootSearchFields: opts.rootSearchFields, diff --git a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts index aa79a10b2a9be..858770579fb9e 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts @@ -35,6 +35,7 @@ interface GetSearchDslOptions { sortField?: string; sortOrder?: string; namespaces?: string[]; + typeToNamespacesMap?: Map; hasReference?: { type: string; id: string; @@ -56,6 +57,7 @@ export function getSearchDsl( sortField, sortOrder, namespaces, + typeToNamespacesMap, hasReference, kueryNode, } = options; @@ -74,6 +76,7 @@ export function getSearchDsl( registry, namespaces, type, + typeToNamespacesMap, search, searchFields, rootSearchFields, diff --git a/src/core/server/saved_objects/service/lib/utils.test.ts b/src/core/server/saved_objects/service/lib/utils.test.ts index ea4fa68242bea..ac06ca9275783 100644 --- a/src/core/server/saved_objects/service/lib/utils.test.ts +++ b/src/core/server/saved_objects/service/lib/utils.test.ts @@ -17,10 +17,11 @@ * under the License. */ +import { SavedObjectsFindOptions } from '../../types'; import { SavedObjectsUtils } from './utils'; describe('SavedObjectsUtils', () => { - const { namespaceIdToString, namespaceStringToId } = SavedObjectsUtils; + const { namespaceIdToString, namespaceStringToId, createEmptyFindResponse } = SavedObjectsUtils; describe('#namespaceIdToString', () => { it('converts `undefined` to default namespace string', () => { @@ -54,4 +55,26 @@ describe('SavedObjectsUtils', () => { test(''); }); }); + + describe('#createEmptyFindResponse', () => { + it('returns expected result', () => { + const options = {} as SavedObjectsFindOptions; + expect(createEmptyFindResponse(options)).toEqual({ + page: 1, + per_page: 20, + total: 0, + saved_objects: [], + }); + }); + + it('handles `page` field', () => { + const options = { page: 42 } as SavedObjectsFindOptions; + expect(createEmptyFindResponse(options).page).toEqual(42); + }); + + it('handles `perPage` field', () => { + const options = { perPage: 42 } as SavedObjectsFindOptions; + expect(createEmptyFindResponse(options).per_page).toEqual(42); + }); + }); }); diff --git a/src/core/server/saved_objects/service/lib/utils.ts b/src/core/server/saved_objects/service/lib/utils.ts index 6101ad57cc401..3efe8614da1d7 100644 --- a/src/core/server/saved_objects/service/lib/utils.ts +++ b/src/core/server/saved_objects/service/lib/utils.ts @@ -17,7 +17,12 @@ * under the License. */ +import { SavedObjectsFindOptions } from '../../types'; +import { SavedObjectsFindResponse } from '..'; + export const DEFAULT_NAMESPACE_STRING = 'default'; +export const FIND_DEFAULT_PAGE = 1; +export const FIND_DEFAULT_PER_PAGE = 20; /** * @public @@ -50,4 +55,17 @@ export class SavedObjectsUtils { return namespace !== DEFAULT_NAMESPACE_STRING ? namespace : undefined; }; + + /** + * Creates an empty response for a find operation. This is only intended to be used by saved objects client wrappers. + */ + public static createEmptyFindResponse = ({ + page = FIND_DEFAULT_PAGE, + perPage = FIND_DEFAULT_PER_PAGE, + }: SavedObjectsFindOptions): SavedObjectsFindResponse => ({ + page, + per_page: perPage, + total: 0, + saved_objects: [], + }); } diff --git a/src/core/server/saved_objects/types.ts b/src/core/server/saved_objects/types.ts index 1885f5ec50139..01128e4f8cf51 100644 --- a/src/core/server/saved_objects/types.ts +++ b/src/core/server/saved_objects/types.ts @@ -89,6 +89,14 @@ export interface SavedObjectsFindOptions { defaultSearchOperator?: 'AND' | 'OR'; filter?: string | KueryNode; namespaces?: string[]; + /** + * This map defines each type to search for, and the namespace(s) to search for the type in; this is only intended to be used by a saved + * object client wrapper. + * If this is defined, it supersedes the `type` and `namespaces` fields when building the Elasticsearch query. + * Any types that are not included in this map will be excluded entirely. + * If a type is included but its value is undefined, the operation will search for that type in the Default namespace. + */ + typeToNamespacesMap?: Map; /** An optional ES preference value to be used for the query **/ preference?: string; } diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index d755ef3e1b676..cc51d27589ce7 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -864,10 +864,6 @@ export interface IndexSettingsDeprecationInfo { // @public (undocumented) export interface IRenderOptions { - // @internal @deprecated - app?: { - getId(): string; - }; includeUserSettings?: boolean; // @internal @deprecated vars?: Record; @@ -1286,21 +1282,6 @@ export class LegacyElasticsearchErrorHelpers { static isNotAuthorizedError(error: any): error is LegacyElasticsearchError; } -// Warning: (ae-forgotten-export) The symbol "ILegacyInternals" needs to be exported by the entry point index.d.ts -// -// @internal @deprecated (undocumented) -export class LegacyInternals implements ILegacyInternals { - constructor(uiExports: LegacyUiExports, config: LegacyConfig, server: Server); - // (undocumented) - getInjectedUiAppVars(id: string): Promise>; - // (undocumented) - getVars(id: string, request: KibanaRequest | LegacyRequest, injected?: LegacyVars): Promise>; - // Warning: (ae-forgotten-export) The symbol "VarsInjector" needs to be exported by the entry point index.d.ts - // - // (undocumented) - injectUiAppVars(id: string, injector: VarsInjector): void; - } - // @public @deprecated (undocumented) export interface LegacyRequest extends Request { } @@ -1312,16 +1293,6 @@ export class LegacyScopedClusterClient implements ILegacyScopedClusterClient { callAsInternalUser(endpoint: string, clientParams?: Record, options?: LegacyCallAPIOptions): Promise; } -// Warning: (ae-forgotten-export) The symbol "LegacyPlugins" needs to be exported by the entry point index.d.ts -// -// @internal @deprecated (undocumented) -export interface LegacyServiceDiscoverPlugins extends LegacyPlugins { - // (undocumented) - pluginExtendedConfig: LegacyConfig; - // (undocumented) - settings: LegacyVars; -} - // @public @deprecated (undocumented) export interface LegacyServiceSetupDeps { // Warning: (ae-forgotten-export) The symbol "LegacyCoreSetup" needs to be exported by the entry point index.d.ts @@ -1346,31 +1317,6 @@ export interface LegacyServiceStartDeps { plugins: Record; } -// @internal @deprecated (undocumented) -export interface LegacyUiExports { - // Warning: (ae-forgotten-export) The symbol "VarsProvider" needs to be exported by the entry point index.d.ts - // - // (undocumented) - defaultInjectedVarProviders?: VarsProvider[]; - // Warning: (ae-forgotten-export) The symbol "VarsReplacer" needs to be exported by the entry point index.d.ts - // - // (undocumented) - injectedVarsReplacers?: VarsReplacer[]; - // Warning: (ae-forgotten-export) The symbol "LegacyNavLinkSpec" needs to be exported by the entry point index.d.ts - // - // (undocumented) - navLinkSpecs?: LegacyNavLinkSpec[] | null; - // Warning: (ae-forgotten-export) The symbol "LegacyAppSpec" needs to be exported by the entry point index.d.ts - // - // (undocumented) - uiAppSpecs?: Array; - // (undocumented) - unknown?: [{ - pluginSpec: LegacyPluginSpec; - type: unknown; - }]; -} - // Warning: (ae-forgotten-export) The symbol "lifecycleResponseFactory" needs to be exported by the entry point index.d.ts // // @public @@ -2177,6 +2123,7 @@ export interface SavedObjectsFindOptions { sortOrder?: string; // (undocumented) type: string | string[]; + typeToNamespacesMap?: Map; } // @public @@ -2388,7 +2335,7 @@ export class SavedObjectsRepository { deleteByNamespace(namespace: string, options?: SavedObjectsDeleteByNamespaceOptions): Promise; deleteFromNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsDeleteFromNamespacesOptions): Promise; // (undocumented) - find({ search, defaultSearchOperator, searchFields, rootSearchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespaces, type, filter, preference, }: SavedObjectsFindOptions): Promise>; + find(options: SavedObjectsFindOptions): Promise>; get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; incrementCounter(type: string, id: string, counterFieldName: string, options?: SavedObjectsIncrementCounterOptions): Promise; update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; @@ -2496,6 +2443,7 @@ export interface SavedObjectsUpdateResponse extends Omit({ page, perPage, }: SavedObjectsFindOptions) => SavedObjectsFindResponse; static namespaceIdToString: (namespace?: string | undefined) => string; static namespaceStringToId: (namespace: string) => string | undefined; } @@ -2732,7 +2680,6 @@ export const validBodyOutput: readonly ["data", "stream"]; // Warnings were encountered during analysis: // // src/core/server/http/router/response.ts:316:3 - (ae-forgotten-export) The symbol "KibanaResponse" needs to be exported by the entry point index.d.ts -// src/core/server/legacy/types.ts:135:16 - (ae-forgotten-export) The symbol "LegacyPluginSpec" needs to be exported by the entry point index.d.ts // src/core/server/plugins/types.ts:274:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts // src/core/server/plugins/types.ts:274:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts // src/core/server/plugins/types.ts:277:3 - (ae-forgotten-export) The symbol "SavedObjectsConfigType" needs to be exported by the entry point index.d.ts diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 8502f563cb0c2..5935636d54f9d 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -113,11 +113,12 @@ export class Server { const { pluginTree, uiPlugins } = await this.plugins.discover({ environment: environmentSetup, }); - const legacyPlugins = await this.legacy.discoverPlugins(); + const legacyConfigSetup = await this.legacy.setupLegacyConfig(); // Immediately terminate in case of invalid configuration + // This needs to be done after plugin discovery await this.configService.validate(); - await ensureValidConfiguration(this.configService, legacyPlugins); + await ensureValidConfiguration(this.configService, legacyConfigSetup); const contextServiceSetup = this.context.setup({ // We inject a fake "legacy plugin" with dependencies on every plugin so that legacy plugins: @@ -166,7 +167,6 @@ export class Server { const renderingSetup = await this.rendering.setup({ http: httpSetup, status: statusSetup, - legacyPlugins, uiPlugins, }); @@ -248,10 +248,6 @@ export class Server { await this.http.start(); - await this.rendering.start({ - legacy: this.legacy, - }); - return this.coreStart; } diff --git a/src/core/server/status/plugins_status.test.ts b/src/core/server/status/plugins_status.test.ts index a75dc8c283698..176e2414a8d04 100644 --- a/src/core/server/status/plugins_status.test.ts +++ b/src/core/server/status/plugins_status.test.ts @@ -161,13 +161,13 @@ describe('PluginStatusService', () => { }, b: { level: ServiceStatusLevels.degraded, - summary: '[2] services are degraded', + summary: '[savedObjects]: savedObjects degraded', detail: 'See the status page for more information', meta: expect.any(Object), }, c: { level: ServiceStatusLevels.degraded, - summary: '[3] services are degraded', + summary: '[savedObjects]: savedObjects degraded', detail: 'See the status page for more information', meta: expect.any(Object), }, @@ -186,13 +186,13 @@ describe('PluginStatusService', () => { }, b: { level: ServiceStatusLevels.critical, - summary: '[2] services are critical', + summary: '[elasticsearch]: elasticsearch critical', detail: 'See the status page for more information', meta: expect.any(Object), }, c: { level: ServiceStatusLevels.critical, - summary: '[3] services are critical', + summary: '[elasticsearch]: elasticsearch critical', detail: 'See the status page for more information', meta: expect.any(Object), }, diff --git a/src/core/server/status/plugins_status.ts b/src/core/server/status/plugins_status.ts index 113d59b327c11..988f2d9969ccb 100644 --- a/src/core/server/status/plugins_status.ts +++ b/src/core/server/status/plugins_status.ts @@ -33,7 +33,17 @@ interface Deps { export class PluginsStatusService { private readonly pluginStatuses = new Map>(); private readonly update$ = new BehaviorSubject(true); - constructor(private readonly deps: Deps) {} + private readonly defaultInheritedStatus$: Observable; + + constructor(private readonly deps: Deps) { + this.defaultInheritedStatus$ = this.deps.core$.pipe( + map((coreStatus) => { + return getSummaryStatus(Object.entries(coreStatus), { + allAvailableSummary: `All dependencies are available`, + }); + }) + ); + } public set(plugin: PluginName, status$: Observable) { this.pluginStatuses.set(plugin, status$); @@ -57,14 +67,24 @@ export class PluginsStatusService { } public getDerivedStatus$(plugin: PluginName): Observable { - return combineLatest([this.deps.core$, this.getDependenciesStatus$(plugin)]).pipe( - map(([coreStatus, pluginStatuses]) => { - return getSummaryStatus( - [...Object.entries(coreStatus), ...Object.entries(pluginStatuses)], - { - allAvailableSummary: `All dependencies are available`, - } - ); + return this.update$.pipe( + switchMap(() => { + // Only go up the dependency tree if any of this plugin's dependencies have a custom status + // Helps eliminate memory overhead of creating thousands of Observables unnecessarily. + if (this.anyCustomStatuses(plugin)) { + return combineLatest([this.deps.core$, this.getDependenciesStatus$(plugin)]).pipe( + map(([coreStatus, pluginStatuses]) => { + return getSummaryStatus( + [...Object.entries(coreStatus), ...Object.entries(pluginStatuses)], + { + allAvailableSummary: `All dependencies are available`, + } + ); + }) + ); + } else { + return this.defaultInheritedStatus$; + } }) ); } @@ -95,4 +115,17 @@ export class PluginsStatusService { }) ); } + + /** + * Determines whether or not this plugin or any plugin in it's dependency tree have a custom status registered. + */ + private anyCustomStatuses(plugin: PluginName): boolean { + if (this.pluginStatuses.get(plugin)) { + return true; + } + + return this.deps.pluginDependencies + .get(plugin)! + .reduce((acc, depName) => acc || this.anyCustomStatuses(depName), false as boolean); + } } diff --git a/src/core/server/status/status_service.ts b/src/core/server/status/status_service.ts index 9acf93f2f8197..62f226405e81a 100644 --- a/src/core/server/status/status_service.ts +++ b/src/core/server/status/status_service.ts @@ -70,10 +70,10 @@ export class StatusService implements CoreService { const core$ = this.setupCoreStatus({ elasticsearch, savedObjects }); this.pluginsStatus = new PluginsStatusService({ core$, pluginDependencies }); - const overall$: Observable = combineLatest( + const overall$: Observable = combineLatest([ core$, - this.pluginsStatus.getAll$() - ).pipe( + this.pluginsStatus.getAll$(), + ]).pipe( // Prevent many emissions at once from dependency status resolution from making this too noisy debounceTime(500), map(([coreStatus, pluginsStatus]) => { diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker index b02b7cc16ec4a..884e7e38494cc 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker @@ -235,6 +235,7 @@ kibana_vars=( xpack.security.cookieName xpack.security.enabled xpack.security.encryptionKey + xpack.security.sameSiteCookies xpack.security.secureCookies xpack.security.sessionTimeout xpack.security.session.idleTimeout diff --git a/src/dev/jest/config.js b/src/dev/jest/config.js index 5d31db63773fa..3c556a4f1ba3c 100644 --- a/src/dev/jest/config.js +++ b/src/dev/jest/config.js @@ -55,7 +55,6 @@ export default { '@elastic/eui$': '/node_modules/@elastic/eui/test-env', '@elastic/eui/lib/(.*)?': '/node_modules/@elastic/eui/test-env/$1', '^src/plugins/(.*)': '/src/plugins/$1', - '^uiExports/(.*)': '/src/dev/jest/mocks/file_mock.js', '^test_utils/(.*)': '/src/test_utils/public/$1', '^fixtures/(.*)': '/src/fixtures/$1', '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': diff --git a/src/fixtures/stubbed_saved_object_index_pattern.ts b/src/fixtures/stubbed_saved_object_index_pattern.ts index 44b391f14cf9c..261e451db5452 100644 --- a/src/fixtures/stubbed_saved_object_index_pattern.ts +++ b/src/fixtures/stubbed_saved_object_index_pattern.ts @@ -28,10 +28,10 @@ export function stubbedSavedObjectIndexPattern(id: string | null = null) { type: 'index-pattern', attributes: { timeFieldName: 'timestamp', - customFormats: '{}', + customFormats: {}, fields: mockLogstashFields, title: 'title', }, - version: 2, + version: '2', }; } diff --git a/src/legacy/plugin_discovery/README.md b/src/legacy/plugin_discovery/README.md deleted file mode 100644 index 83e7c10d16fff..0000000000000 --- a/src/legacy/plugin_discovery/README.md +++ /dev/null @@ -1,148 +0,0 @@ -# Plugin Discovery - -The plugin discovery module defines the core plugin loading logic used by the Kibana server. It exports functions for - - -## `findPluginSpecs(settings, [config])` - -Finds [`PluginSpec`][PluginSpec] objects - -### params - - `settings`: the same settings object accepted by [`KbnServer`][KbnServer] - - `[config]`: Optional - a [`Config`][Config] service. Using this param causes `findPluginSpecs()` to modify `config`'s schema to support the configuration for each discovered [`PluginSpec`][PluginSpec]. If you can, please use the [`Config`][Config] service produced by `extendedConfig$` rather than passing in an existing service so that `findPluginSpecs()` is side-effect free. - -### return value - -`findPluginSpecs()` returns an object of Observables which produce values at different parts of the process. Since the Observables are all aware of their own dependencies you can subscribe to any combination (within the same tick) and only the necessary plugin logic will be executed. - -If you *never* subscribe to any of the Observables then plugin discovery won't actually run. - - - `pack$`: emits every [`PluginPack`][PluginPack] found - - `invalidDirectoryError$: Observable`: emits [`InvalidDirectoryError`][Errors]s caused by `settings.plugins.scanDirs` values that don't point to actual directories. `findPluginSpecs()` will not abort when this error is encountered. - - `invalidPackError$: Observable`: emits [`InvalidPackError`][Errors]s caused by children of `settings.plugins.scanDirs` or `settings.plugins.paths` values which don't meet the requirements of a [`PluginPack`][PluginPack] (probably missing a `package.json`). `findPluginSpecs()` will not abort when this error is encountered. - - `deprecation$: Observable`: emits deprecation warnings that are produces when reading each [`PluginPack`][PluginPack]'s configuration - - `extendedConfig$: Observable`: emits the [`Config`][Config] service that was passed to `findPluginSpecs()` (or created internally if none was passed) after it has been extended with the configuration from each plugin - - `spec$: Observable`: emits every *enabled* [`PluginSpec`][PluginSpec] defined by the discovered [`PluginPack`][PluginPack]s - - `disabledSpec$: Observable`: emits every *disabled* [`PluginSpec`][PluginSpec] defined by the discovered [`PluginPack`][PluginPack]s - - `invalidVersionSpec$: Observable`: emits every [`PluginSpec`][PluginSpec] who's required kibana version does not match the version exposed by `config.get('pkg.version')` - -### example - -Just get the plugin specs, only fail if there is an uncaught error of some sort: -```js -const { pack$ } = findPluginSpecs(settings); -const packs = await pack$.pipe(toArray()).toPromise() -``` - -Just log the deprecation messages: -```js -const { deprecation$ } = findPluginSpecs(settings); -for (const warning of await deprecation$.pipe(toArray()).toPromise()) { - console.log('DEPRECATION:', warning) -} -``` - -Get the packs but fail if any packs are invalid: -```js -const { pack$, invalidDirectoryError$ } = findPluginSpecs(settings); -const packs = await Rx.merge( - pack$.pipe(toArray()), - - // if we ever get an InvalidDirectoryError, throw it - // into the stream so that all streams are unsubscribed, - // the discovery process is aborted, and the promise rejects - invalidDirectoryError$.pipe( - map(error => { throw error }) - ), -).toPromise() -``` - -Handle everything -```js -const { - pack$, - invalidDirectoryError$, - invalidPackError$, - deprecation$, - extendedConfig$, - spec$, - disabledSpecs$, - invalidVersionSpec$, -} = findPluginSpecs(settings); - -Rx.merge( - pack$.pipe( - tap(pluginPack => console.log('Found plugin pack', pluginPack)) - ), - - invalidDirectoryError$.pipe( - tap(error => console.log('Invalid directory error', error)) - ), - - invalidPackError$.pipe( - tap(error => console.log('Invalid plugin pack error', error)) - ), - - deprecation$.pipe( - tap(msg => console.log('DEPRECATION:', msg)) - ), - - extendedConfig$.pipe( - tap(config => console.log('config service extended by plugins', config)) - ), - - spec$.pipe( - tap(pluginSpec => console.log('enabled plugin spec found', spec)) - ), - - disabledSpec$.pipe( - tap(pluginSpec => console.log('disabled plugin spec found', spec)) - ), - - invalidVersionSpec$.pipe( - tap(pluginSpec => console.log('plugin spec with invalid version found', spec)) - ), -) -.toPromise() -.then(() => { - console.log('plugin discovery complete') -}) -.catch((error) => { - console.log('plugin discovery failed', error) -}) - -``` - -## `reduceExportSpecs(pluginSpecs, reducers, [defaults={}])` - -Reduces every value exported by the [`PluginSpec`][PluginSpec]s to produce a single value. If an exported value is an array each item in the array will be reduced individually. If the exported value is `undefined` it will be ignored. The reducer is called with the signature: - -```js -reducer( - // the result of the previous reducer call, or `defaults` - acc: any, - // the exported value, found at `uiExports[type]` or `uiExports[type][i]` - // in the PluginSpec config. - spec: any, - // the key in `uiExports` where this export was found - type: string, - // the PluginSpec which exported this spec - pluginSpec: PluginSpec -) -``` - -## `new PluginPack(options)` class - -Only exported so that `PluginPack` instances can be created in tests and used in place of on-disk plugin fixtures. Use `findPluginSpecs()`, or the cached result of a call to `findPluginSpecs()` (like `kbnServer.pluginSpecs`) any time you might need access to `PluginPack` objects in distributed code. - -### params - - - `options.path`: absolute path to where this plugin pack was found, this is normally a direct child of `./src/legacy/core_plugins` or `./plugins` - - `options.pkg`: the parsed `package.json` for this pack, used for defaults in `PluginSpec` objects defined by this pack - - `options.provider`: the default export of the pack, a function which is called with the `PluginSpec` class which should return one or more `PluginSpec` objects. - -[PluginPack]: ./plugin_pack/plugin_pack.js "PluginPath class definition" -[PluginSpec]: ./plugin_spec/plugin_spec.js "PluginSpec class definition" -[Errors]: ./errors.js "PluginDiscover specific error types" -[KbnServer]: ../server/kbn_server.js "KbnServer class definition" -[Config]: ../server/config/config.js "KbnServer/Config class definition" diff --git a/src/legacy/plugin_discovery/__tests__/find_plugin_specs.js b/src/legacy/plugin_discovery/__tests__/find_plugin_specs.js deleted file mode 100644 index e6af23d69c549..0000000000000 --- a/src/legacy/plugin_discovery/__tests__/find_plugin_specs.js +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { resolve } from 'path'; -import { toArray } from 'rxjs/operators'; - -import expect from '@kbn/expect'; -import { isEqual } from 'lodash'; -import { findPluginSpecs } from '../find_plugin_specs'; -import { PluginSpec } from '../plugin_spec'; - -const PLUGIN_FIXTURES = resolve(__dirname, 'fixtures/plugins'); -const CONFLICT_FIXTURES = resolve(__dirname, 'fixtures/conflicts'); - -describe('plugin discovery', () => { - describe('findPluginSpecs()', function () { - this.timeout(10000); - - describe('spec$', () => { - it('finds specs for specified plugin paths', async () => { - const { spec$ } = findPluginSpecs({ - plugins: { - paths: [ - resolve(PLUGIN_FIXTURES, 'foo'), - resolve(PLUGIN_FIXTURES, 'bar'), - resolve(PLUGIN_FIXTURES, 'broken'), - ], - }, - }); - - const specs = await spec$.pipe(toArray()).toPromise(); - expect(specs).to.have.length(3); - specs.forEach((spec) => { - expect(spec).to.be.a(PluginSpec); - }); - expect(specs.map((s) => s.getId()).sort()).to.eql(['bar:one', 'bar:two', 'foo']); - }); - - it('finds all specs in scanDirs', async () => { - const { spec$ } = findPluginSpecs({ - // used to ensure the dev_mode plugin is enabled - env: 'development', - - plugins: { - scanDirs: [PLUGIN_FIXTURES], - }, - }); - - const specs = await spec$.pipe(toArray()).toPromise(); - expect(specs).to.have.length(3); - specs.forEach((spec) => { - expect(spec).to.be.a(PluginSpec); - }); - expect(specs.map((s) => s.getId()).sort()).to.eql(['bar:one', 'bar:two', 'foo']); - }); - - it('does not find disabled plugins', async () => { - const { spec$ } = findPluginSpecs({ - 'bar:one': { - enabled: false, - }, - - plugins: { - paths: [ - resolve(PLUGIN_FIXTURES, 'foo'), - resolve(PLUGIN_FIXTURES, 'bar'), - resolve(PLUGIN_FIXTURES, 'broken'), - ], - }, - }); - - const specs = await spec$.pipe(toArray()).toPromise(); - expect(specs).to.have.length(2); - specs.forEach((spec) => { - expect(spec).to.be.a(PluginSpec); - }); - expect(specs.map((s) => s.getId()).sort()).to.eql(['bar:two', 'foo']); - }); - - it('dedupes duplicate packs', async () => { - const { spec$ } = findPluginSpecs({ - plugins: { - scanDirs: [PLUGIN_FIXTURES], - paths: [ - resolve(PLUGIN_FIXTURES, 'foo'), - resolve(PLUGIN_FIXTURES, 'foo'), - resolve(PLUGIN_FIXTURES, 'bar'), - resolve(PLUGIN_FIXTURES, 'bar'), - resolve(PLUGIN_FIXTURES, 'broken'), - resolve(PLUGIN_FIXTURES, 'broken'), - ], - }, - }); - - const specs = await spec$.pipe(toArray()).toPromise(); - expect(specs).to.have.length(3); - specs.forEach((spec) => { - expect(spec).to.be.a(PluginSpec); - }); - expect(specs.map((s) => s.getId()).sort()).to.eql(['bar:one', 'bar:two', 'foo']); - }); - - describe('conflicting plugin spec ids', () => { - it('fails with informative message', async () => { - const { spec$ } = findPluginSpecs({ - plugins: { - scanDirs: [], - paths: [resolve(CONFLICT_FIXTURES, 'foo')], - }, - }); - - try { - await spec$.pipe(toArray()).toPromise(); - throw new Error('expected spec$ to throw an error'); - } catch (error) { - expect(error.message).to.contain('Multiple plugins found with the id "foo"'); - expect(error.message).to.contain(CONFLICT_FIXTURES); - } - }); - }); - }); - - describe('packageJson$', () => { - const checkPackageJsons = (packageJsons) => { - expect(packageJsons).to.have.length(2); - const package1 = packageJsons.find((packageJson) => - isEqual( - { - directoryPath: resolve(PLUGIN_FIXTURES, 'foo'), - contents: { - name: 'foo', - version: 'kibana', - }, - }, - packageJson - ) - ); - expect(package1).to.be.an(Object); - const package2 = packageJsons.find((packageJson) => - isEqual( - { - directoryPath: resolve(PLUGIN_FIXTURES, 'bar'), - contents: { - name: 'foo', - version: 'kibana', - }, - }, - packageJson - ) - ); - expect(package2).to.be.an(Object); - }; - - it('finds packageJson for specified plugin paths', async () => { - const { packageJson$ } = findPluginSpecs({ - plugins: { - paths: [ - resolve(PLUGIN_FIXTURES, 'foo'), - resolve(PLUGIN_FIXTURES, 'bar'), - resolve(PLUGIN_FIXTURES, 'broken'), - ], - }, - }); - - const packageJsons = await packageJson$.pipe(toArray()).toPromise(); - checkPackageJsons(packageJsons); - }); - - it('finds all packageJsons in scanDirs', async () => { - const { packageJson$ } = findPluginSpecs({ - // used to ensure the dev_mode plugin is enabled - env: 'development', - - plugins: { - scanDirs: [PLUGIN_FIXTURES], - }, - }); - - const packageJsons = await packageJson$.pipe(toArray()).toPromise(); - checkPackageJsons(packageJsons); - }); - - it('dedupes duplicate packageJson', async () => { - const { packageJson$ } = findPluginSpecs({ - plugins: { - scanDirs: [PLUGIN_FIXTURES], - paths: [ - resolve(PLUGIN_FIXTURES, 'foo'), - resolve(PLUGIN_FIXTURES, 'foo'), - resolve(PLUGIN_FIXTURES, 'bar'), - resolve(PLUGIN_FIXTURES, 'bar'), - resolve(PLUGIN_FIXTURES, 'broken'), - resolve(PLUGIN_FIXTURES, 'broken'), - ], - }, - }); - - const packageJsons = await packageJson$.pipe(toArray()).toPromise(); - checkPackageJsons(packageJsons); - }); - }); - }); -}); diff --git a/src/legacy/plugin_discovery/__tests__/fixtures/conflicts/foo/index.js b/src/legacy/plugin_discovery/__tests__/fixtures/conflicts/foo/index.js deleted file mode 100644 index fcbe3487463b7..0000000000000 --- a/src/legacy/plugin_discovery/__tests__/fixtures/conflicts/foo/index.js +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export default function (kibana) { - return [ - // two plugins exported without ids will both inherit - // the id of the pack and conflict - new kibana.Plugin({}), - new kibana.Plugin({}), - ]; -} diff --git a/src/legacy/plugin_discovery/__tests__/fixtures/conflicts/foo/package.json b/src/legacy/plugin_discovery/__tests__/fixtures/conflicts/foo/package.json deleted file mode 100644 index e43c2f0bc984c..0000000000000 --- a/src/legacy/plugin_discovery/__tests__/fixtures/conflicts/foo/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "foo", - "version": "kibana" -} diff --git a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/bar/package.json b/src/legacy/plugin_discovery/__tests__/fixtures/plugins/bar/package.json deleted file mode 100644 index e43c2f0bc984c..0000000000000 --- a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/bar/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "foo", - "version": "kibana" -} diff --git a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/broken/index.js b/src/legacy/plugin_discovery/__tests__/fixtures/plugins/broken/index.js deleted file mode 100644 index 59f4a2649f019..0000000000000 --- a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/broken/index.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export default { - foo: 'bar', -}; diff --git a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/broken/package1.json b/src/legacy/plugin_discovery/__tests__/fixtures/plugins/broken/package1.json deleted file mode 100644 index 81ddb6221d515..0000000000000 --- a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/broken/package1.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "baz", - "version": "kibana" -} diff --git a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/foo/index.js b/src/legacy/plugin_discovery/__tests__/fixtures/plugins/foo/index.js deleted file mode 100644 index e43a1dcedb372..0000000000000 --- a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/foo/index.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -module.exports = function (kibana) { - return new kibana.Plugin({ - id: 'foo', - }); -}; diff --git a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/foo/package.json b/src/legacy/plugin_discovery/__tests__/fixtures/plugins/foo/package.json deleted file mode 100644 index e43c2f0bc984c..0000000000000 --- a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/foo/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "foo", - "version": "kibana" -} diff --git a/src/legacy/plugin_discovery/errors.js b/src/legacy/plugin_discovery/errors.js deleted file mode 100644 index 02d81b32d1fd1..0000000000000 --- a/src/legacy/plugin_discovery/errors.js +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const errorCodeProperty = Symbol('pluginDiscovery/errorCode'); - -/** - * Thrown when reading a plugin directory fails, wraps failure - * @type {String} - */ -const ERROR_INVALID_DIRECTORY = 'ERROR_INVALID_DIRECTORY'; -export function createInvalidDirectoryError(sourceError, path) { - sourceError[errorCodeProperty] = ERROR_INVALID_DIRECTORY; - sourceError.path = path; - return sourceError; -} -export function isInvalidDirectoryError(error) { - return error && error[errorCodeProperty] === ERROR_INVALID_DIRECTORY; -} - -/** - * Thrown when trying to create a PluginPack for a path that - * is not a valid plugin definition - * @type {String} - */ -const ERROR_INVALID_PACK = 'ERROR_INVALID_PACK'; -export function createInvalidPackError(path, reason) { - const error = new Error(`PluginPack${path ? ` at "${path}"` : ''} ${reason}`); - error[errorCodeProperty] = ERROR_INVALID_PACK; - error.path = path; - return error; -} -export function isInvalidPackError(error) { - return error && error[errorCodeProperty] === ERROR_INVALID_PACK; -} - -/** - * Thrown when trying to load a PluginSpec that is invalid for some reason - * @type {String} - */ -const ERROR_INVALID_PLUGIN = 'ERROR_INVALID_PLUGIN'; -export function createInvalidPluginError(spec, reason) { - const error = new Error( - `Plugin from ${spec.getId()} at ${spec.getPack().getPath()} is invalid because ${reason}` - ); - error[errorCodeProperty] = ERROR_INVALID_PLUGIN; - error.spec = spec; - return error; -} -export function isInvalidPluginError(error) { - return error && error[errorCodeProperty] === ERROR_INVALID_PLUGIN; -} - -/** - * Thrown when trying to load a PluginSpec whose version is incompatible - * @type {String} - */ -const ERROR_INCOMPATIBLE_PLUGIN_VERSION = 'ERROR_INCOMPATIBLE_PLUGIN_VERSION'; -export function createIncompatiblePluginVersionError(spec) { - const error = new Error( - `Plugin ${spec.getId()} is only compatible with Kibana version ${spec.getExpectedKibanaVersion()}` - ); - error[errorCodeProperty] = ERROR_INCOMPATIBLE_PLUGIN_VERSION; - error.spec = spec; - return error; -} -export function isIncompatiblePluginVersionError(error) { - return error && error[errorCodeProperty] === ERROR_INCOMPATIBLE_PLUGIN_VERSION; -} diff --git a/src/legacy/plugin_discovery/find_plugin_specs.js b/src/legacy/plugin_discovery/find_plugin_specs.js deleted file mode 100644 index b97476bb456a5..0000000000000 --- a/src/legacy/plugin_discovery/find_plugin_specs.js +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import * as Rx from 'rxjs'; -import { - distinct, - toArray, - mergeMap, - share, - shareReplay, - filter, - last, - map, - tap, -} from 'rxjs/operators'; -import { realpathSync } from 'fs'; - -import { Config } from '../server/config'; - -import { extendConfigService, disableConfigExtension } from './plugin_config'; - -import { - createPack$, - createPackageJsonAtPath$, - createPackageJsonsInDirectory$, -} from './plugin_pack'; - -import { isInvalidDirectoryError, isInvalidPackError } from './errors'; - -export function defaultConfig(settings) { - return Config.withDefaultSchema(settings); -} - -function bufferAllResults(observable) { - return observable.pipe( - // buffer all results into a single array - toArray(), - // merge the array back into the stream when complete - mergeMap((array) => array) - ); -} - -/** - * Determine a distinct value for each result from find$ - * so they can be deduplicated - * @param {{error?,pack?}} result - * @return {Any} - */ -function getDistinctKeyForFindResult(result) { - // errors are distinct by their message - if (result.error) { - return result.error.message; - } - - // packs are distinct by their absolute and real path - if (result.packageJson) { - return realpathSync(result.packageJson.directoryPath); - } - - // non error/pack results shouldn't exist, but if they do they are all unique - return result; -} - -function groupSpecsById(specs) { - const specsById = new Map(); - for (const spec of specs) { - const id = spec.getId(); - if (specsById.has(id)) { - specsById.get(id).push(spec); - } else { - specsById.set(id, [spec]); - } - } - return specsById; -} - -/** - * Creates a collection of observables for discovering pluginSpecs - * using Kibana's defaults, settings, and config service - * - * @param {Object} settings - * @param {ConfigService} [configToMutate] when supplied **it is mutated** to - * include the config from discovered plugin specs - * @return {Object} - */ -export function findPluginSpecs(settings, configToMutate) { - const config$ = Rx.defer(async () => { - if (configToMutate) { - return configToMutate; - } - - return defaultConfig(settings); - }).pipe(shareReplay()); - - // find plugin packs in configured paths/dirs - const packageJson$ = config$.pipe( - mergeMap((config) => - Rx.merge( - ...config.get('plugins.paths').map(createPackageJsonAtPath$), - ...config.get('plugins.scanDirs').map(createPackageJsonsInDirectory$) - ) - ), - distinct(getDistinctKeyForFindResult), - share() - ); - - const pack$ = createPack$(packageJson$).pipe(share()); - - const extendConfig$ = config$.pipe( - mergeMap((config) => - pack$.pipe( - // get the specs for each found plugin pack - mergeMap(({ pack }) => (pack ? pack.getPluginSpecs() : [])), - // make sure that none of the plugin specs have conflicting ids, fail - // early if conflicts detected or merge the specs back into the stream - toArray(), - mergeMap((allSpecs) => { - for (const [id, specs] of groupSpecsById(allSpecs)) { - if (specs.length > 1) { - throw new Error( - `Multiple plugins found with the id "${id}":\n${specs - .map((spec) => ` - ${id} at ${spec.getPath()}`) - .join('\n')}` - ); - } - } - - return allSpecs; - }), - mergeMap(async (spec) => { - // extend the config service with this plugin spec and - // collect its deprecations messages if some of its - // settings are outdated - const deprecations = []; - await extendConfigService(spec, config, settings, (message) => { - deprecations.push({ spec, message }); - }); - - return { - spec, - deprecations, - }; - }), - // extend the config with all plugins before determining enabled status - bufferAllResults, - map(({ spec, deprecations }) => { - const isRightVersion = spec.isVersionCompatible(config.get('pkg.version')); - const enabled = isRightVersion && spec.isEnabled(config); - return { - config, - spec, - deprecations, - enabledSpecs: enabled ? [spec] : [], - disabledSpecs: enabled ? [] : [spec], - invalidVersionSpecs: isRightVersion ? [] : [spec], - }; - }), - // determine which plugins are disabled before actually removing things from the config - bufferAllResults, - tap((result) => { - for (const spec of result.disabledSpecs) { - disableConfigExtension(spec, config); - } - }) - ) - ), - share() - ); - - return { - // package JSONs found when searching configure paths - packageJson$: packageJson$.pipe( - mergeMap((result) => (result.packageJson ? [result.packageJson] : [])) - ), - - // plugin packs found when searching configured paths - pack$: pack$.pipe(mergeMap((result) => (result.pack ? [result.pack] : []))), - - // errors caused by invalid directories of plugin directories - invalidDirectoryError$: pack$.pipe( - mergeMap((result) => (isInvalidDirectoryError(result.error) ? [result.error] : [])) - ), - - // errors caused by directories that we expected to be plugin but were invalid - invalidPackError$: pack$.pipe( - mergeMap((result) => (isInvalidPackError(result.error) ? [result.error] : [])) - ), - - otherError$: pack$.pipe( - mergeMap((result) => (isUnhandledError(result.error) ? [result.error] : [])) - ), - - // { spec, message } objects produced when transforming deprecated - // settings for a plugin spec - deprecation$: extendConfig$.pipe(mergeMap((result) => result.deprecations)), - - // the config service we extended with all of the plugin specs, - // only emitted once it is fully extended by all - extendedConfig$: extendConfig$.pipe( - mergeMap((result) => result.config), - filter(Boolean), - last() - ), - - // all enabled PluginSpec objects - spec$: extendConfig$.pipe(mergeMap((result) => result.enabledSpecs)), - - // all disabled PluginSpec objects - disabledSpec$: extendConfig$.pipe(mergeMap((result) => result.disabledSpecs)), - - // all PluginSpec objects that were disabled because their version was incompatible - invalidVersionSpec$: extendConfig$.pipe(mergeMap((result) => result.invalidVersionSpecs)), - }; -} - -function isUnhandledError(error) { - return error != null && !isInvalidDirectoryError(error) && !isInvalidPackError(error); -} diff --git a/src/legacy/plugin_discovery/index.js b/src/legacy/plugin_discovery/index.js deleted file mode 100644 index b60806f6cbc23..0000000000000 --- a/src/legacy/plugin_discovery/index.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { findPluginSpecs } from './find_plugin_specs'; -export { reduceExportSpecs } from './plugin_exports'; -export { PluginPack } from './plugin_pack'; diff --git a/src/legacy/plugin_discovery/plugin_config/__tests__/extend_config_service.js b/src/legacy/plugin_discovery/plugin_config/__tests__/extend_config_service.js deleted file mode 100644 index 40f84f6f54b3b..0000000000000 --- a/src/legacy/plugin_discovery/plugin_config/__tests__/extend_config_service.js +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import sinon from 'sinon'; -import expect from '@kbn/expect'; - -import { Config } from '../../../server/config'; -import { PluginPack } from '../../plugin_pack'; -import { extendConfigService, disableConfigExtension } from '../extend_config_service'; -import * as SchemaNS from '../schema'; -import * as SettingsNS from '../settings'; - -describe('plugin discovery/extend config service', () => { - const sandbox = sinon.createSandbox(); - afterEach(() => sandbox.restore()); - - const pluginSpec = new PluginPack({ - path: '/dev/null', - pkg: { - name: 'test', - version: 'kibana', - }, - provider: ({ Plugin }) => - new Plugin({ - configPrefix: 'foo.bar.baz', - - config: (Joi) => - Joi.object({ - enabled: Joi.boolean().default(true), - test: Joi.string().default('bonk'), - }).default(), - }), - }) - .getPluginSpecs() - .pop(); - - describe('extendConfigService()', () => { - it('calls getSettings, getSchema, and Config.extendSchema() correctly', async () => { - const rootSettings = { - foo: { - bar: { - enabled: false, - }, - }, - }; - const schema = { - validate: () => {}, - }; - const configPrefix = 'foo.bar'; - const config = { - extendSchema: sandbox.stub(), - }; - const pluginSpec = { - getConfigPrefix: sandbox.stub().returns(configPrefix), - }; - - const getSettings = sandbox.stub(SettingsNS, 'getSettings').returns(rootSettings.foo.bar); - - const getSchema = sandbox.stub(SchemaNS, 'getSchema').returns(schema); - - await extendConfigService(pluginSpec, config, rootSettings); - - sinon.assert.calledOnce(getSettings); - sinon.assert.calledWithExactly(getSettings, pluginSpec, rootSettings); - - sinon.assert.calledOnce(getSchema); - sinon.assert.calledWithExactly(getSchema, pluginSpec); - - sinon.assert.calledOnce(config.extendSchema); - sinon.assert.calledWithExactly( - config.extendSchema, - schema, - rootSettings.foo.bar, - configPrefix - ); - }); - - it('adds the schema for a plugin spec to its config prefix', async () => { - const config = Config.withDefaultSchema(); - expect(config.has('foo.bar.baz')).to.be(false); - await extendConfigService(pluginSpec, config); - expect(config.has('foo.bar.baz')).to.be(true); - }); - - it('initializes it with the default settings', async () => { - const config = Config.withDefaultSchema(); - await extendConfigService(pluginSpec, config); - expect(config.get('foo.bar.baz.enabled')).to.be(true); - expect(config.get('foo.bar.baz.test')).to.be('bonk'); - }); - - it('initializes it with values from root settings if defined', async () => { - const config = Config.withDefaultSchema(); - await extendConfigService(pluginSpec, config, { - foo: { - bar: { - baz: { - test: 'hello world', - }, - }, - }, - }); - - expect(config.get('foo.bar.baz.test')).to.be('hello world'); - }); - - it('throws if root settings are invalid', async () => { - const config = Config.withDefaultSchema(); - try { - await extendConfigService(pluginSpec, config, { - foo: { - bar: { - baz: { - test: { - 'not a string': true, - }, - }, - }, - }, - }); - throw new Error('Expected extendConfigService() to throw because of bad settings'); - } catch (error) { - expect(error.message).to.contain('"test" must be a string'); - } - }); - }); - - describe('disableConfigExtension()', () => { - it('removes added config', async () => { - const config = Config.withDefaultSchema(); - await extendConfigService(pluginSpec, config); - expect(config.has('foo.bar.baz.test')).to.be(true); - await disableConfigExtension(pluginSpec, config); - expect(config.has('foo.bar.baz.test')).to.be(false); - }); - - it('leaves {configPrefix}.enabled config', async () => { - const config = Config.withDefaultSchema(); - expect(config.has('foo.bar.baz.enabled')).to.be(false); - await extendConfigService(pluginSpec, config); - expect(config.get('foo.bar.baz.enabled')).to.be(true); - await disableConfigExtension(pluginSpec, config); - expect(config.get('foo.bar.baz.enabled')).to.be(false); - }); - }); -}); diff --git a/src/legacy/plugin_discovery/plugin_config/__tests__/schema.js b/src/legacy/plugin_discovery/plugin_config/__tests__/schema.js deleted file mode 100644 index 78adb1e680e20..0000000000000 --- a/src/legacy/plugin_discovery/plugin_config/__tests__/schema.js +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; - -import { PluginPack } from '../../plugin_pack'; -import { getSchema, getStubSchema } from '../schema'; - -describe('plugin discovery/schema', () => { - function createPluginSpec(configProvider) { - return new PluginPack({ - path: '/dev/null', - pkg: { - name: 'test', - version: 'kibana', - }, - provider: ({ Plugin }) => - new Plugin({ - configPrefix: 'foo.bar.baz', - config: configProvider, - }), - }) - .getPluginSpecs() - .pop(); - } - - describe('getSchema()', () => { - it('calls the config provider and returns its return value', async () => { - const pluginSpec = createPluginSpec(() => 'foo'); - expect(await getSchema(pluginSpec)).to.be('foo'); - }); - - it('supports config provider that returns a promise', async () => { - const pluginSpec = createPluginSpec(() => Promise.resolve('foo')); - expect(await getSchema(pluginSpec)).to.be('foo'); - }); - - it('uses default schema when no config provider', async () => { - const schema = await getSchema(createPluginSpec()); - expect(schema).to.be.an('object'); - expect(schema).to.have.property('validate').a('function'); - expect(schema.validate({}).value).to.eql({ - enabled: true, - }); - }); - - it('uses default schema when config returns falsy value', async () => { - const schema = await getSchema(createPluginSpec(() => null)); - expect(schema).to.be.an('object'); - expect(schema).to.have.property('validate').a('function'); - expect(schema.validate({}).value).to.eql({ - enabled: true, - }); - }); - - it('uses default schema when config promise resolves to falsy value', async () => { - const schema = await getSchema(createPluginSpec(() => Promise.resolve(null))); - expect(schema).to.be.an('object'); - expect(schema).to.have.property('validate').a('function'); - expect(schema.validate({}).value).to.eql({ - enabled: true, - }); - }); - }); - - describe('getStubSchema()', () => { - it('returns schema with enabled: false', async () => { - const schema = await getStubSchema(); - expect(schema).to.be.an('object'); - expect(schema).to.have.property('validate').a('function'); - expect(schema.validate({}).value).to.eql({ - enabled: false, - }); - }); - }); -}); diff --git a/src/legacy/plugin_discovery/plugin_config/__tests__/settings.js b/src/legacy/plugin_discovery/plugin_config/__tests__/settings.js deleted file mode 100644 index 750c5ee6c6f50..0000000000000 --- a/src/legacy/plugin_discovery/plugin_config/__tests__/settings.js +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; - -import { PluginPack } from '../../plugin_pack'; -import { getSettings } from '../settings'; - -describe('plugin_discovery/settings', () => { - const pluginSpec = new PluginPack({ - path: '/dev/null', - pkg: { - name: 'test', - version: 'kibana', - }, - provider: ({ Plugin }) => - new Plugin({ - configPrefix: 'a.b.c', - }), - }) - .getPluginSpecs() - .pop(); - - describe('getSettings()', () => { - it('reads settings from config prefix', async () => { - const rootSettings = { - a: { - b: { - c: { - enabled: false, - }, - }, - }, - }; - - expect(await getSettings(pluginSpec, rootSettings)).to.eql({ - enabled: false, - }); - }); - - it('allows rootSettings to be undefined', async () => { - expect(await getSettings(pluginSpec)).to.eql(undefined); - }); - }); -}); diff --git a/src/legacy/plugin_discovery/plugin_config/extend_config_service.js b/src/legacy/plugin_discovery/plugin_config/extend_config_service.js deleted file mode 100644 index a6d5d4ae5f990..0000000000000 --- a/src/legacy/plugin_discovery/plugin_config/extend_config_service.js +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { getSettings } from './settings'; -import { getSchema, getStubSchema } from './schema'; - -/** - * Extend a config service with the schema and settings for a - * plugin spec and optionally call logDeprecation with warning - * messages about deprecated settings that are used - * @param {PluginSpec} spec - * @param {Server.Config} config - * @param {Object} rootSettings - * @param {Function} [logDeprecation] - * @return {Promise} - */ -export async function extendConfigService(spec, config, rootSettings) { - const settings = await getSettings(spec, rootSettings); - const schema = await getSchema(spec); - config.extendSchema(schema, settings, spec.getConfigPrefix()); -} - -/** - * Disable the schema and settings applied to a config service for - * a plugin spec - * @param {PluginSpec} spec - * @param {Server.Config} config - * @return {undefined} - */ -export function disableConfigExtension(spec, config) { - const prefix = spec.getConfigPrefix(); - config.removeSchema(prefix); - config.extendSchema(getStubSchema(), { enabled: false }, prefix); -} diff --git a/src/legacy/plugin_discovery/plugin_config/index.js b/src/legacy/plugin_discovery/plugin_config/index.js deleted file mode 100644 index a27463bc9c7f5..0000000000000 --- a/src/legacy/plugin_discovery/plugin_config/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { extendConfigService, disableConfigExtension } from './extend_config_service'; diff --git a/src/legacy/plugin_discovery/plugin_config/schema.js b/src/legacy/plugin_discovery/plugin_config/schema.js deleted file mode 100644 index 14d10aa5568da..0000000000000 --- a/src/legacy/plugin_discovery/plugin_config/schema.js +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import Joi from 'joi'; - -const STUB_CONFIG_SCHEMA = Joi.object() - .keys({ - enabled: Joi.valid(false).default(false), - }) - .default(); - -const DEFAULT_CONFIG_SCHEMA = Joi.object() - .keys({ - enabled: Joi.boolean().default(true), - }) - .default(); - -/** - * Get the config schema for a plugin spec - * @param {PluginSpec} spec - * @return {Promise} - */ -export async function getSchema(spec) { - const provider = spec.getConfigSchemaProvider(); - return (provider && (await provider(Joi))) || DEFAULT_CONFIG_SCHEMA; -} - -export function getStubSchema() { - return STUB_CONFIG_SCHEMA; -} diff --git a/src/legacy/plugin_discovery/plugin_config/settings.js b/src/legacy/plugin_discovery/plugin_config/settings.js deleted file mode 100644 index e6a4741d76eca..0000000000000 --- a/src/legacy/plugin_discovery/plugin_config/settings.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { get } from 'lodash'; - -/** - * Get the settings for a pluginSpec from the raw root settings while - * optionally calling logDeprecation() with warnings about deprecated - * settings that were used - * @param {PluginSpec} spec - * @param {Object} rootSettings - * @return {Promise} - */ -export async function getSettings(spec, rootSettings) { - const prefix = spec.getConfigPrefix(); - const rawSettings = get(rootSettings, prefix); - return rawSettings; -} diff --git a/src/legacy/plugin_discovery/plugin_exports/__tests__/reduce_export_specs.js b/src/legacy/plugin_discovery/plugin_exports/__tests__/reduce_export_specs.js deleted file mode 100644 index 3beaacc1a8293..0000000000000 --- a/src/legacy/plugin_discovery/plugin_exports/__tests__/reduce_export_specs.js +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; - -import { PluginPack } from '../../plugin_pack'; -import { reduceExportSpecs } from '../reduce_export_specs'; - -const PLUGIN = new PluginPack({ - path: __dirname, - pkg: { - name: 'foo', - version: 'kibana', - }, - provider: ({ Plugin }) => - new Plugin({ - uiExports: { - concatNames: { - name: 'export1', - }, - - concat: ['export2', 'export3'], - }, - }), -}); - -const REDUCERS = { - concatNames(acc, spec, type, pluginSpec) { - return { - names: [].concat(acc.names || [], `${pluginSpec.getId()}:${spec.name}`), - }; - }, - concat(acc, spec, type, pluginSpec) { - return { - names: [].concat(acc.names || [], `${pluginSpec.getId()}:${spec}`), - }; - }, -}; - -const PLUGIN_SPECS = PLUGIN.getPluginSpecs(); - -describe('reduceExportSpecs', () => { - it('combines ui exports from a list of plugin definitions', () => { - const exports = reduceExportSpecs(PLUGIN_SPECS, REDUCERS); - expect(exports).to.eql({ - names: ['foo:export1', 'foo:export2', 'foo:export3'], - }); - }); - - it('starts with the defaults', () => { - const exports = reduceExportSpecs(PLUGIN_SPECS, REDUCERS, { - names: ['default'], - }); - - expect(exports).to.eql({ - names: ['default', 'foo:export1', 'foo:export2', 'foo:export3'], - }); - }); -}); diff --git a/src/legacy/plugin_discovery/plugin_exports/reduce_export_specs.js b/src/legacy/plugin_discovery/plugin_exports/reduce_export_specs.js deleted file mode 100644 index a3adc3091085d..0000000000000 --- a/src/legacy/plugin_discovery/plugin_exports/reduce_export_specs.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * Combine the exportSpecs from a list of pluginSpecs - * by calling the reducers for each export type - * @param {Array} pluginSpecs - * @param {Object} reducers - * @param {Object} [defaults={}] - * @return {Object} - */ -export function reduceExportSpecs(pluginSpecs, reducers, defaults = {}) { - return pluginSpecs.reduce((acc, pluginSpec) => { - const specsByType = pluginSpec.getExportSpecs() || {}; - const types = Object.keys(specsByType); - - return types.reduce((acc, type) => { - const reducer = reducers[type] || reducers.unknown; - - if (!reducer) { - throw new Error(`Unknown export type ${type}`); - } - - // convert specs to an array if not already one or - // ignore the spec if it is undefined - const specs = [].concat(specsByType[type] === undefined ? [] : specsByType[type]); - - return specs.reduce((acc, spec) => reducer(acc, spec, type, pluginSpec), acc); - }, acc); - }, defaults); -} diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/create_pack.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/create_pack.js deleted file mode 100644 index b17bd69479ffa..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/create_pack.js +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { resolve } from 'path'; -import * as Rx from 'rxjs'; -import { toArray } from 'rxjs/operators'; -import expect from '@kbn/expect'; - -import { createPack$ } from '../create_pack'; -import { PluginPack } from '../plugin_pack'; - -import { PLUGINS_DIR, assertInvalidPackError } from './utils'; - -describe('plugin discovery/create pack', () => { - it('creates PluginPack', async () => { - const packageJson$ = Rx.from([ - { - packageJson: { - directoryPath: resolve(PLUGINS_DIR, 'prebuilt'), - contents: { - name: 'prebuilt', - }, - }, - }, - ]); - const results = await createPack$(packageJson$).pipe(toArray()).toPromise(); - expect(results).to.have.length(1); - expect(results[0]).to.only.have.keys(['pack']); - const { pack } = results[0]; - expect(pack).to.be.a(PluginPack); - }); - - describe('errors thrown', () => { - async function checkError(path, check) { - const packageJson$ = Rx.from([ - { - packageJson: { - directoryPath: path, - }, - }, - ]); - - const results = await createPack$(packageJson$).pipe(toArray()).toPromise(); - expect(results).to.have.length(1); - expect(results[0]).to.only.have.keys(['error']); - const { error } = results[0]; - await check(error); - } - it('default export is an object', () => - checkError(resolve(PLUGINS_DIR, 'exports_object'), (error) => { - assertInvalidPackError(error); - expect(error.message).to.contain('must export a function'); - })); - it('default export is an number', () => - checkError(resolve(PLUGINS_DIR, 'exports_number'), (error) => { - assertInvalidPackError(error); - expect(error.message).to.contain('must export a function'); - })); - it('default export is an string', () => - checkError(resolve(PLUGINS_DIR, 'exports_string'), (error) => { - assertInvalidPackError(error); - expect(error.message).to.contain('must export a function'); - })); - it('directory with code that fails when required', () => - checkError(resolve(PLUGINS_DIR, 'broken_code'), (error) => { - expect(error.message).to.contain("Cannot find module 'does-not-exist'"); - })); - }); -}); diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken/package.json b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken/package.json deleted file mode 100644 index f830e8b60c02d..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": -} diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken_code/index.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken_code/index.js deleted file mode 100644 index bdb26504d6b6e..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken_code/index.js +++ /dev/null @@ -1,7 +0,0 @@ -const brokenRequire = require('does-not-exist'); // eslint-disable-line - -module.exports = function (kibana) { - return new kibana.Plugin({ - id: 'foo', - }); -}; diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken_code/package.json b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken_code/package.json deleted file mode 100644 index e43c2f0bc984c..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken_code/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "foo", - "version": "kibana" -} diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_number/index.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_number/index.js deleted file mode 100644 index f24fc54e38d9a..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_number/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export default 1; diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_number/package.json b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_number/package.json deleted file mode 100644 index e43c2f0bc984c..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_number/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "foo", - "version": "kibana" -} diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_object/package.json b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_object/package.json deleted file mode 100644 index e43c2f0bc984c..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_object/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "foo", - "version": "kibana" -} diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_string/package.json b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_string/package.json deleted file mode 100644 index e43c2f0bc984c..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_string/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "foo", - "version": "kibana" -} diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/foo/index.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/foo/index.js deleted file mode 100644 index e43a1dcedb372..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/foo/index.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -module.exports = function (kibana) { - return new kibana.Plugin({ - id: 'foo', - }); -}; diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/foo/package.json b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/foo/package.json deleted file mode 100644 index e43c2f0bc984c..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/foo/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "foo", - "version": "kibana" -} diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/index.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/index.js deleted file mode 100644 index edb1dd15673da..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -console.log('hello world'); diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/lib/index.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/lib/index.js deleted file mode 100644 index 050ffdfbde9ea..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/lib/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { myLib } from './my_lib'; diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/lib/my_lib.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/lib/my_lib.js deleted file mode 100644 index 94e511632d9a6..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/lib/my_lib.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export function myLib() { - console.log('lib'); -} diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/prebuilt/index.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/prebuilt/index.js deleted file mode 100644 index 89744b2dd3fd9..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/prebuilt/index.js +++ /dev/null @@ -1,14 +0,0 @@ -/* eslint-disable */ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -exports.default = function (_ref) { - var Plugin = _ref.Plugin; - - return new Plugin({ - id: 'foo' - }); -}; diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/prebuilt/package.json b/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/prebuilt/package.json deleted file mode 100644 index b1b74e0e76b12..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/prebuilt/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "prebuilt" -} diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/package_json_at_path.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/package_json_at_path.js deleted file mode 100644 index fa1033180954e..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/package_json_at_path.js +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { resolve } from 'path'; -import { toArray } from 'rxjs/operators'; - -import expect from '@kbn/expect'; - -import { createPackageJsonAtPath$ } from '../package_json_at_path'; -import { PLUGINS_DIR, assertInvalidPackError, assertInvalidDirectoryError } from './utils'; - -describe('plugin discovery/plugin_pack', () => { - describe('createPackageJsonAtPath$()', () => { - it('returns an observable', () => { - expect(createPackageJsonAtPath$()).to.have.property('subscribe').a('function'); - }); - it('gets the default provider from prebuilt babel modules', async () => { - const results = await createPackageJsonAtPath$(resolve(PLUGINS_DIR, 'prebuilt')) - .pipe(toArray()) - .toPromise(); - expect(results).to.have.length(1); - expect(results[0]).to.only.have.keys(['packageJson']); - expect(results[0].packageJson).to.be.an(Object); - expect(results[0].packageJson.directoryPath).to.be(resolve(PLUGINS_DIR, 'prebuilt')); - expect(results[0].packageJson.contents).to.eql({ name: 'prebuilt' }); - }); - describe('errors emitted as { error } results', () => { - async function checkError(path, check) { - const results = await createPackageJsonAtPath$(path).pipe(toArray()).toPromise(); - expect(results).to.have.length(1); - expect(results[0]).to.only.have.keys(['error']); - const { error } = results[0]; - await check(error); - } - it('undefined path', () => - checkError(undefined, (error) => { - assertInvalidDirectoryError(error); - expect(error.message).to.contain('path must be a string'); - })); - it('relative path', () => - checkError('plugins/foo', (error) => { - assertInvalidDirectoryError(error); - expect(error.message).to.contain('path must be absolute'); - })); - it('./relative path', () => - checkError('./plugins/foo', (error) => { - assertInvalidDirectoryError(error); - expect(error.message).to.contain('path must be absolute'); - })); - it('non-existent path', () => - checkError(resolve(PLUGINS_DIR, 'baz'), (error) => { - assertInvalidPackError(error); - expect(error.message).to.contain('must be a directory'); - })); - it('path to a file', () => - checkError(resolve(PLUGINS_DIR, 'index.js'), (error) => { - assertInvalidPackError(error); - expect(error.message).to.contain('must be a directory'); - })); - it('directory without a package.json', () => - checkError(resolve(PLUGINS_DIR, 'lib'), (error) => { - assertInvalidPackError(error); - expect(error.message).to.contain('must have a package.json file'); - })); - it('directory with an invalid package.json', () => - checkError(resolve(PLUGINS_DIR, 'broken'), (error) => { - assertInvalidPackError(error); - expect(error.message).to.contain('must have a valid package.json file'); - })); - }); - }); -}); diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/package_jsons_in_directory.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/package_jsons_in_directory.js deleted file mode 100644 index 37cb4cc064da7..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/package_jsons_in_directory.js +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { resolve } from 'path'; - -import { toArray } from 'rxjs/operators'; -import expect from '@kbn/expect'; - -import { createPackageJsonsInDirectory$ } from '../package_jsons_in_directory'; - -import { PLUGINS_DIR, assertInvalidDirectoryError } from './utils'; - -describe('plugin discovery/packs in directory', () => { - describe('createPackageJsonsInDirectory$()', () => { - describe('errors emitted as { error } results', () => { - async function checkError(path, check) { - const results = await createPackageJsonsInDirectory$(path).pipe(toArray()).toPromise(); - expect(results).to.have.length(1); - expect(results[0]).to.only.have.keys('error'); - const { error } = results[0]; - await check(error); - } - - it('undefined path', () => - checkError(undefined, (error) => { - assertInvalidDirectoryError(error); - expect(error.message).to.contain('path must be a string'); - })); - it('relative path', () => - checkError('my/plugins', (error) => { - assertInvalidDirectoryError(error); - expect(error.message).to.contain('path must be absolute'); - })); - it('./relative path', () => - checkError('./my/pluginsd', (error) => { - assertInvalidDirectoryError(error); - expect(error.message).to.contain('path must be absolute'); - })); - it('non-existent path', () => - checkError(resolve(PLUGINS_DIR, 'notreal'), (error) => { - assertInvalidDirectoryError(error); - expect(error.message).to.contain('no such file or directory'); - })); - it('path to a file', () => - checkError(resolve(PLUGINS_DIR, 'index.js'), (error) => { - assertInvalidDirectoryError(error); - expect(error.message).to.contain('not a directory'); - })); - }); - - it('includes child errors for invalid packageJsons within a valid directory', async () => { - const results = await createPackageJsonsInDirectory$(PLUGINS_DIR).pipe(toArray()).toPromise(); - - const errors = results.map((result) => result.error).filter(Boolean); - - const packageJsons = results.map((result) => result.packageJson).filter(Boolean); - - packageJsons.forEach((pack) => expect(pack).to.be.an(Object)); - // there should be one result for each item in PLUGINS_DIR - expect(results).to.have.length(8); - // three of the fixtures are errors of some sort - expect(errors).to.have.length(2); - // six of them are valid - expect(packageJsons).to.have.length(6); - }); - }); -}); diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/plugin_pack.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/plugin_pack.js deleted file mode 100644 index 769fcd74ce6fb..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/plugin_pack.js +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import sinon from 'sinon'; - -import { PluginPack } from '../plugin_pack'; -import { PluginSpec } from '../../plugin_spec'; - -describe('plugin discovery/plugin pack', () => { - describe('constructor', () => { - it('requires an object', () => { - expect(() => { - new PluginPack(); - }).to.throwError(); - }); - }); - describe('#getPkg()', () => { - it('returns the `pkg` constructor argument', () => { - const pkg = {}; - const pack = new PluginPack({ pkg }); - expect(pack.getPkg()).to.be(pkg); - }); - }); - describe('#getPath()', () => { - it('returns the `path` constructor argument', () => { - const path = {}; - const pack = new PluginPack({ path }); - expect(pack.getPath()).to.be(path); - }); - }); - describe('#getPluginSpecs()', () => { - it('calls the `provider` constructor argument with an api including a single sub class of PluginSpec', () => { - const provider = sinon.stub(); - const pack = new PluginPack({ provider }); - sinon.assert.notCalled(provider); - pack.getPluginSpecs(); - sinon.assert.calledOnce(provider); - sinon.assert.calledWithExactly(provider, { - Plugin: sinon.match((Class) => { - return Class.prototype instanceof PluginSpec; - }, 'Subclass of PluginSpec'), - }); - }); - - it('casts undefined return value to array', () => { - const pack = new PluginPack({ provider: () => undefined }); - expect(pack.getPluginSpecs()).to.eql([]); - }); - - it('casts single PluginSpec to an array', () => { - const pack = new PluginPack({ - path: '/dev/null', - pkg: { name: 'foo', version: 'kibana' }, - provider: ({ Plugin }) => new Plugin({}), - }); - - const specs = pack.getPluginSpecs(); - expect(specs).to.be.an('array'); - expect(specs).to.have.length(1); - expect(specs[0]).to.be.a(PluginSpec); - }); - - it('returns an array of PluginSpec', () => { - const pack = new PluginPack({ - path: '/dev/null', - pkg: { name: 'foo', version: 'kibana' }, - provider: ({ Plugin }) => [new Plugin({}), new Plugin({})], - }); - - const specs = pack.getPluginSpecs(); - expect(specs).to.be.an('array'); - expect(specs).to.have.length(2); - expect(specs[0]).to.be.a(PluginSpec); - expect(specs[1]).to.be.a(PluginSpec); - }); - - it('throws if non-undefined return value is not an instance of api.Plugin', () => { - let OtherPluginSpecClass; - const otherPack = new PluginPack({ - path: '/dev/null', - pkg: { name: 'foo', version: 'kibana' }, - provider: (api) => { - OtherPluginSpecClass = api.Plugin; - }, - }); - - // call getPluginSpecs() on other pack to get it's api.Plugin class - otherPack.getPluginSpecs(); - - const badPacks = [ - new PluginPack({ provider: () => false }), - new PluginPack({ provider: () => null }), - new PluginPack({ provider: () => 1 }), - new PluginPack({ provider: () => 'true' }), - new PluginPack({ provider: () => true }), - new PluginPack({ provider: () => new Date() }), - new PluginPack({ provider: () => /foo.*bar/ }), - new PluginPack({ provider: () => function () {} }), - new PluginPack({ provider: () => new OtherPluginSpecClass({}) }), - ]; - - for (const pack of badPacks) { - expect(() => pack.getPluginSpecs()).to.throwError((error) => { - expect(error.message).to.contain('unexpected plugin export'); - }); - } - }); - }); -}); diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/utils.js b/src/legacy/plugin_discovery/plugin_pack/__tests__/utils.js deleted file mode 100644 index adcf60d809ff7..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/utils.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { resolve } from 'path'; -import { inspect } from 'util'; - -import { isInvalidPackError, isInvalidDirectoryError } from '../../errors'; - -export const PLUGINS_DIR = resolve(__dirname, 'fixtures/plugins'); - -export function assertInvalidDirectoryError(error) { - if (!isInvalidDirectoryError(error)) { - throw new Error(`Expected ${inspect(error)} to be an 'InvalidDirectoryError'`); - } -} - -export function assertInvalidPackError(error) { - if (!isInvalidPackError(error)) { - throw new Error(`Expected ${inspect(error)} to be an 'InvalidPackError'`); - } -} diff --git a/src/legacy/plugin_discovery/plugin_pack/create_pack.js b/src/legacy/plugin_discovery/plugin_pack/create_pack.js deleted file mode 100644 index 189c2ea324103..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/create_pack.js +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { PluginPack } from './plugin_pack'; -import { map, catchError } from 'rxjs/operators'; -import { createInvalidPackError } from '../errors'; - -function createPack(packageJson) { - let provider = require(packageJson.directoryPath); // eslint-disable-line import/no-dynamic-require - if (provider.__esModule) { - provider = provider.default; - } - if (typeof provider !== 'function') { - throw createInvalidPackError(packageJson.directoryPath, 'must export a function'); - } - - return new PluginPack({ path: packageJson.directoryPath, pkg: packageJson.contents, provider }); -} - -export const createPack$ = (packageJson$) => - packageJson$.pipe( - map(({ error, packageJson }) => { - if (error) { - return { error }; - } - - if (!packageJson) { - throw new Error('packageJson is required to create the pack'); - } - - return { - pack: createPack(packageJson), - }; - }), - // createPack can throw errors, and we want them to be represented - // like the errors we consume from createPackageJsonAtPath/Directory - catchError((error) => [{ error }]) - ); diff --git a/src/legacy/plugin_discovery/plugin_pack/index.js b/src/legacy/plugin_discovery/plugin_pack/index.js deleted file mode 100644 index 69e55baee660b..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/index.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { createPack$ } from './create_pack'; -export { createPackageJsonAtPath$ } from './package_json_at_path'; -export { createPackageJsonsInDirectory$ } from './package_jsons_in_directory'; -export { PluginPack } from './plugin_pack'; diff --git a/src/legacy/plugin_discovery/plugin_pack/lib/fs.js b/src/legacy/plugin_discovery/plugin_pack/lib/fs.js deleted file mode 100644 index 2b531e314df52..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/lib/fs.js +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { stat, readdir } from 'fs'; -import { resolve, isAbsolute } from 'path'; - -import { fromNode as fcb } from 'bluebird'; -import * as Rx from 'rxjs'; -import { catchError, mergeAll, filter, map, mergeMap } from 'rxjs/operators'; - -import { createInvalidDirectoryError } from '../../errors'; - -function assertAbsolutePath(path) { - if (typeof path !== 'string') { - throw createInvalidDirectoryError(new TypeError('path must be a string'), path); - } - - if (!isAbsolute(path)) { - throw createInvalidDirectoryError(new TypeError('path must be absolute'), path); - } -} - -async function statTest(path, test) { - try { - const stats = await fcb((cb) => stat(path, cb)); - return Boolean(test(stats)); - } catch (error) { - if (error.code !== 'ENOENT') { - throw error; - } - } - return false; -} - -/** - * Determine if a path currently points to a directory - * @param {String} path - * @return {Promise} - */ -export async function isDirectory(path) { - assertAbsolutePath(path); - return await statTest(path, (stat) => stat.isDirectory()); -} - -/** - * Get absolute paths for child directories within a path - * @param {string} path - * @return {Promise>} - */ -export const createChildDirectory$ = (path) => - Rx.defer(() => { - assertAbsolutePath(path); - return fcb((cb) => readdir(path, cb)); - }).pipe( - catchError((error) => { - throw createInvalidDirectoryError(error, path); - }), - mergeAll(), - filter((name) => !name.startsWith('.')), - map((name) => resolve(path, name)), - mergeMap(async (absolute) => { - if (await isDirectory(absolute)) { - return [absolute]; - } else { - return []; - } - }), - mergeAll() - ); diff --git a/src/legacy/plugin_discovery/plugin_pack/lib/index.js b/src/legacy/plugin_discovery/plugin_pack/lib/index.js deleted file mode 100644 index 491deeda27516..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/lib/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { isDirectory, createChildDirectory$ } from './fs'; diff --git a/src/legacy/plugin_discovery/plugin_pack/package_json_at_path.js b/src/legacy/plugin_discovery/plugin_pack/package_json_at_path.js deleted file mode 100644 index 18629ef3ea802..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/package_json_at_path.js +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { readFileSync } from 'fs'; -import * as Rx from 'rxjs'; -import { map, mergeMap, catchError } from 'rxjs/operators'; -import { resolve } from 'path'; -import { createInvalidPackError } from '../errors'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { isNewPlatformPlugin } from '../../../core/server/plugins'; - -import { isDirectory } from './lib'; - -async function createPackageJsonAtPath(path) { - if (!(await isDirectory(path))) { - throw createInvalidPackError(path, 'must be a directory'); - } - - let str; - try { - str = readFileSync(resolve(path, 'package.json')); - } catch (err) { - throw createInvalidPackError(path, 'must have a package.json file'); - } - - let pkg; - try { - pkg = JSON.parse(str); - } catch (err) { - throw createInvalidPackError(path, 'must have a valid package.json file'); - } - - return { - directoryPath: path, - contents: pkg, - }; -} - -export const createPackageJsonAtPath$ = (path) => - // If plugin directory contains manifest file, we should skip it since it - // should have been handled by the core plugin system already. - Rx.defer(() => isNewPlatformPlugin(path)).pipe( - mergeMap((isNewPlatformPlugin) => (isNewPlatformPlugin ? [] : createPackageJsonAtPath(path))), - map((packageJson) => ({ packageJson })), - catchError((error) => [{ error }]) - ); diff --git a/src/legacy/plugin_discovery/plugin_pack/package_jsons_in_directory.js b/src/legacy/plugin_discovery/plugin_pack/package_jsons_in_directory.js deleted file mode 100644 index 5f0977f4829b8..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/package_jsons_in_directory.js +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { mergeMap, catchError } from 'rxjs/operators'; -import { isInvalidDirectoryError } from '../errors'; - -import { createChildDirectory$ } from './lib'; -import { createPackageJsonAtPath$ } from './package_json_at_path'; - -/** - * Finds the plugins within a directory. Results are - * an array of objects with either `pack` or `error` - * keys. - * - * - `{ error }` results are provided when the path is not - * a directory, or one of the child directories is not a - * valid plugin pack. - * - `{ pack }` results are for discovered plugins defs - * - * @param {String} path - * @return {Array<{pack}|{error}>} - */ -export const createPackageJsonsInDirectory$ = (path) => - createChildDirectory$(path).pipe( - mergeMap(createPackageJsonAtPath$), - catchError((error) => { - // this error is produced by createChildDirectory$() when the path - // is invalid, we return them as an error result similar to how - // createPackAtPath$ works when it finds invalid packs in a directory - if (isInvalidDirectoryError(error)) { - return [{ error }]; - } - - throw error; - }) - ); diff --git a/src/legacy/plugin_discovery/plugin_pack/plugin_pack.js b/src/legacy/plugin_discovery/plugin_pack/plugin_pack.js deleted file mode 100644 index 1baf3d104ca84..0000000000000 --- a/src/legacy/plugin_discovery/plugin_pack/plugin_pack.js +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { inspect } from 'util'; - -import { PluginSpec } from '../plugin_spec'; - -export class PluginPack { - constructor({ path, pkg, provider }) { - this._path = path; - this._pkg = pkg; - this._provider = provider; - } - - /** - * Get the contents of this plugin pack's package.json file - * @return {Object} - */ - getPkg() { - return this._pkg; - } - - /** - * Get the absolute path to this plugin pack on disk - * @return {String} - */ - getPath() { - return this._path; - } - - /** - * Invoke the plugin pack's provider to get the list - * of specs defined in this plugin. - * @return {Array} - */ - getPluginSpecs() { - const pack = this; - const api = { - Plugin: class ScopedPluginSpec extends PluginSpec { - constructor(options) { - super(pack, options); - } - }, - }; - - const result = this._provider(api); - const specs = [].concat(result === undefined ? [] : result); - - // verify that all specs are instances of passed "Plugin" class - specs.forEach((spec) => { - if (!(spec instanceof api.Plugin)) { - throw new TypeError('unexpected plugin export ' + inspect(spec)); - } - }); - - return specs; - } -} diff --git a/src/legacy/plugin_discovery/plugin_spec/__tests__/is_version_compatible.js b/src/legacy/plugin_discovery/plugin_spec/__tests__/is_version_compatible.js deleted file mode 100644 index 897184496af37..0000000000000 --- a/src/legacy/plugin_discovery/plugin_spec/__tests__/is_version_compatible.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; - -import { isVersionCompatible } from '../is_version_compatible'; - -describe('plugin discovery/plugin spec', () => { - describe('isVersionCompatible()', () => { - const tests = [ - ['kibana', '6.0.0', true], - ['kibana', '6.0.0-rc1', true], - ['6.0.0-rc1', '6.0.0', true], - ['6.0.0', '6.0.0-rc1', true], - ['6.0.0-rc2', '6.0.0-rc1', true], - ['6.0.0-rc2', '6.0.0-rc3', true], - ['foo', 'bar', false], - ['6.0.0', '5.1.4', false], - ['5.1.4', '6.0.0', false], - ['5.1.4-SNAPSHOT', '6.0.0-rc2-SNAPSHOT', false], - ['5.1.4', '6.0.0-rc2-SNAPSHOT', false], - ['5.1.4-SNAPSHOT', '6.0.0', false], - ['5.1.4-SNAPSHOT', '6.0.0-rc2', false], - ]; - - for (const [plugin, kibana, shouldPass] of tests) { - it(`${shouldPass ? 'should' : `shouldn't`} allow plugin: ${plugin} kibana: ${kibana}`, () => { - expect(isVersionCompatible(plugin, kibana)).to.be(shouldPass); - }); - } - }); -}); diff --git a/src/legacy/plugin_discovery/plugin_spec/__tests__/plugin_spec.js b/src/legacy/plugin_discovery/plugin_spec/__tests__/plugin_spec.js deleted file mode 100644 index 02675f0bd60f8..0000000000000 --- a/src/legacy/plugin_discovery/plugin_spec/__tests__/plugin_spec.js +++ /dev/null @@ -1,496 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { resolve } from 'path'; - -import expect from '@kbn/expect'; -import sinon from 'sinon'; - -import { PluginPack } from '../../plugin_pack'; -import { PluginSpec } from '../plugin_spec'; -import * as IsVersionCompatibleNS from '../is_version_compatible'; - -const fooPack = new PluginPack({ - path: '/dev/null', - pkg: { name: 'foo', version: 'kibana' }, -}); - -describe('plugin discovery/plugin spec', () => { - describe('PluginSpec', () => { - describe('validation', () => { - it('throws if missing spec.id AND Pack has no name', () => { - const pack = new PluginPack({ pkg: {} }); - expect(() => new PluginSpec(pack, {})).to.throwError((error) => { - expect(error.message).to.contain('Unable to determine plugin id'); - }); - }); - - it('throws if missing spec.kibanaVersion AND Pack has no version', () => { - const pack = new PluginPack({ pkg: { name: 'foo' } }); - expect(() => new PluginSpec(pack, {})).to.throwError((error) => { - expect(error.message).to.contain('Unable to determine plugin version'); - }); - }); - - it('throws if spec.require is defined, but not an array', () => { - function assert(require) { - expect(() => new PluginSpec(fooPack, { require })).to.throwError((error) => { - expect(error.message).to.contain('"plugin.require" must be an array of plugin ids'); - }); - } - - assert(null); - assert(''); - assert('kibana'); - assert(1); - assert(0); - assert(/a.*b/); - }); - - it('throws if spec.publicDir is truthy and not a string', () => { - function assert(publicDir) { - expect(() => new PluginSpec(fooPack, { publicDir })).to.throwError((error) => { - expect(error.message).to.contain( - `The "path" argument must be of type string. Received type ${typeof publicDir}` - ); - }); - } - - assert(1); - assert(function () {}); - assert([]); - assert(/a.*b/); - }); - - it('throws if spec.publicDir is not an absolute path', () => { - function assert(publicDir) { - expect(() => new PluginSpec(fooPack, { publicDir })).to.throwError((error) => { - expect(error.message).to.contain('plugin.publicDir must be an absolute path'); - }); - } - - assert('relative/path'); - assert('./relative/path'); - }); - - it('throws if spec.publicDir basename is not `public`', () => { - function assert(publicDir) { - expect(() => new PluginSpec(fooPack, { publicDir })).to.throwError((error) => { - expect(error.message).to.contain('must end with a "public" directory'); - }); - } - - assert('/www'); - assert('/www/'); - assert('/www/public/my_plugin'); - assert('/www/public/my_plugin/'); - }); - }); - - describe('#getPack()', () => { - it('returns the pack', () => { - const spec = new PluginSpec(fooPack, {}); - expect(spec.getPack()).to.be(fooPack); - }); - }); - - describe('#getPkg()', () => { - it('returns the pkg from the pack', () => { - const spec = new PluginSpec(fooPack, {}); - expect(spec.getPkg()).to.be(fooPack.getPkg()); - }); - }); - - describe('#getPath()', () => { - it('returns the path from the pack', () => { - const spec = new PluginSpec(fooPack, {}); - expect(spec.getPath()).to.be(fooPack.getPath()); - }); - }); - - describe('#getId()', () => { - it('uses spec.id', () => { - const spec = new PluginSpec(fooPack, { - id: 'bar', - }); - - expect(spec.getId()).to.be('bar'); - }); - - it('defaults to pack.pkg.name', () => { - const spec = new PluginSpec(fooPack, {}); - - expect(spec.getId()).to.be('foo'); - }); - }); - - describe('#getVersion()', () => { - it('uses spec.version', () => { - const spec = new PluginSpec(fooPack, { - version: 'bar', - }); - - expect(spec.getVersion()).to.be('bar'); - }); - - it('defaults to pack.pkg.version', () => { - const spec = new PluginSpec(fooPack, {}); - - expect(spec.getVersion()).to.be('kibana'); - }); - }); - - describe('#isEnabled()', () => { - describe('spec.isEnabled is not defined', () => { - function setup(configPrefix, configGetImpl) { - const spec = new PluginSpec(fooPack, { configPrefix }); - const config = { - get: sinon.spy(configGetImpl), - has: sinon.stub(), - }; - - return { spec, config }; - } - - it('throws if not passed a config service', () => { - const { spec } = setup('a.b.c', () => true); - - expect(() => spec.isEnabled()).to.throwError((error) => { - expect(error.message).to.contain('must be called with a config service'); - }); - expect(() => spec.isEnabled(null)).to.throwError((error) => { - expect(error.message).to.contain('must be called with a config service'); - }); - expect(() => spec.isEnabled({ get: () => {} })).to.throwError((error) => { - expect(error.message).to.contain('must be called with a config service'); - }); - }); - - it('returns true when config.get([...configPrefix, "enabled"]) returns true', () => { - const { spec, config } = setup('d.e.f', () => true); - - expect(spec.isEnabled(config)).to.be(true); - sinon.assert.calledOnce(config.get); - sinon.assert.calledWithExactly(config.get, ['d', 'e', 'f', 'enabled']); - }); - - it('returns false when config.get([...configPrefix, "enabled"]) returns false', () => { - const { spec, config } = setup('g.h.i', () => false); - - expect(spec.isEnabled(config)).to.be(false); - sinon.assert.calledOnce(config.get); - sinon.assert.calledWithExactly(config.get, ['g', 'h', 'i', 'enabled']); - }); - }); - - describe('spec.isEnabled is defined', () => { - function setup(isEnabledImpl) { - const isEnabled = sinon.spy(isEnabledImpl); - const spec = new PluginSpec(fooPack, { isEnabled }); - const config = { - get: sinon.stub(), - has: sinon.stub(), - }; - - return { isEnabled, spec, config }; - } - - it('throws if not passed a config service', () => { - const { spec } = setup(() => true); - - expect(() => spec.isEnabled()).to.throwError((error) => { - expect(error.message).to.contain('must be called with a config service'); - }); - expect(() => spec.isEnabled(null)).to.throwError((error) => { - expect(error.message).to.contain('must be called with a config service'); - }); - expect(() => spec.isEnabled({ get: () => {} })).to.throwError((error) => { - expect(error.message).to.contain('must be called with a config service'); - }); - }); - - it('does not check config if spec.isEnabled returns true', () => { - const { spec, isEnabled, config } = setup(() => true); - - expect(spec.isEnabled(config)).to.be(true); - sinon.assert.calledOnce(isEnabled); - sinon.assert.notCalled(config.get); - }); - - it('does not check config if spec.isEnabled returns false', () => { - const { spec, isEnabled, config } = setup(() => false); - - expect(spec.isEnabled(config)).to.be(false); - sinon.assert.calledOnce(isEnabled); - sinon.assert.notCalled(config.get); - }); - }); - }); - - describe('#getExpectedKibanaVersion()', () => { - describe('has: spec.kibanaVersion,pkg.kibana.version,spec.version,pkg.version', () => { - it('uses spec.kibanaVersion', () => { - const pack = new PluginPack({ - path: '/dev/null', - pkg: { - name: 'expkv', - version: '1.0.0', - kibana: { - version: '6.0.0', - }, - }, - }); - - const spec = new PluginSpec(pack, { - version: '2.0.0', - kibanaVersion: '5.0.0', - }); - - expect(spec.getExpectedKibanaVersion()).to.be('5.0.0'); - }); - }); - describe('missing: spec.kibanaVersion, has: pkg.kibana.version,spec.version,pkg.version', () => { - it('uses pkg.kibana.version', () => { - const pack = new PluginPack({ - path: '/dev/null', - pkg: { - name: 'expkv', - version: '1.0.0', - kibana: { - version: '6.0.0', - }, - }, - }); - - const spec = new PluginSpec(pack, { - version: '2.0.0', - }); - - expect(spec.getExpectedKibanaVersion()).to.be('6.0.0'); - }); - }); - describe('missing: spec.kibanaVersion,pkg.kibana.version, has: spec.version,pkg.version', () => { - it('uses spec.version', () => { - const pack = new PluginPack({ - path: '/dev/null', - pkg: { - name: 'expkv', - version: '1.0.0', - }, - }); - - const spec = new PluginSpec(pack, { - version: '2.0.0', - }); - - expect(spec.getExpectedKibanaVersion()).to.be('2.0.0'); - }); - }); - describe('missing: spec.kibanaVersion,pkg.kibana.version,spec.version, has: pkg.version', () => { - it('uses pkg.version', () => { - const pack = new PluginPack({ - path: '/dev/null', - pkg: { - name: 'expkv', - version: '1.0.0', - }, - }); - - const spec = new PluginSpec(pack, {}); - - expect(spec.getExpectedKibanaVersion()).to.be('1.0.0'); - }); - }); - }); - - describe('#isVersionCompatible()', () => { - it('passes this.getExpectedKibanaVersion() and arg to isVersionCompatible(), returns its result', () => { - const spec = new PluginSpec(fooPack, { version: '1.0.0' }); - sinon.stub(spec, 'getExpectedKibanaVersion').returns('foo'); - const isVersionCompatible = sinon - .stub(IsVersionCompatibleNS, 'isVersionCompatible') - .returns('bar'); - expect(spec.isVersionCompatible('baz')).to.be('bar'); - - sinon.assert.calledOnce(spec.getExpectedKibanaVersion); - sinon.assert.calledWithExactly(spec.getExpectedKibanaVersion); - - sinon.assert.calledOnce(isVersionCompatible); - sinon.assert.calledWithExactly(isVersionCompatible, 'foo', 'baz'); - }); - }); - - describe('#getRequiredPluginIds()', () => { - it('returns spec.require', () => { - const spec = new PluginSpec(fooPack, { require: [1, 2, 3] }); - expect(spec.getRequiredPluginIds()).to.eql([1, 2, 3]); - }); - }); - - describe('#getPublicDir()', () => { - describe('spec.publicDir === false', () => { - it('returns null', () => { - const spec = new PluginSpec(fooPack, { publicDir: false }); - expect(spec.getPublicDir()).to.be(null); - }); - }); - - describe('spec.publicDir is falsy', () => { - it('returns public child of pack path', () => { - function assert(publicDir) { - const spec = new PluginSpec(fooPack, { publicDir }); - expect(spec.getPublicDir()).to.be(resolve('/dev/null/public')); - } - - assert(0); - assert(''); - assert(null); - assert(undefined); - assert(NaN); - }); - }); - - describe('spec.publicDir is an absolute path', () => { - it('returns the path', () => { - const spec = new PluginSpec(fooPack, { - publicDir: '/var/www/public', - }); - - expect(spec.getPublicDir()).to.be('/var/www/public'); - }); - }); - - // NOTE: see constructor tests for other truthy-tests that throw in constructor - }); - - describe('#getExportSpecs()', () => { - it('returns spec.uiExports', () => { - const spec = new PluginSpec(fooPack, { - uiExports: 'foo', - }); - - expect(spec.getExportSpecs()).to.be('foo'); - }); - }); - - describe('#getPreInitHandler()', () => { - it('returns spec.preInit', () => { - const spec = new PluginSpec(fooPack, { - preInit: 'foo', - }); - - expect(spec.getPreInitHandler()).to.be('foo'); - }); - }); - - describe('#getInitHandler()', () => { - it('returns spec.init', () => { - const spec = new PluginSpec(fooPack, { - init: 'foo', - }); - - expect(spec.getInitHandler()).to.be('foo'); - }); - }); - - describe('#getConfigPrefix()', () => { - describe('spec.configPrefix is truthy', () => { - it('returns spec.configPrefix', () => { - const spec = new PluginSpec(fooPack, { - configPrefix: 'foo.bar.baz', - }); - - expect(spec.getConfigPrefix()).to.be('foo.bar.baz'); - }); - }); - describe('spec.configPrefix is falsy', () => { - it('returns spec.getId()', () => { - function assert(configPrefix) { - const spec = new PluginSpec(fooPack, { configPrefix }); - sinon.stub(spec, 'getId').returns('foo'); - expect(spec.getConfigPrefix()).to.be('foo'); - sinon.assert.calledOnce(spec.getId); - } - - assert(false); - assert(null); - assert(undefined); - assert(''); - assert(0); - }); - }); - }); - - describe('#getConfigSchemaProvider()', () => { - it('returns spec.config', () => { - const spec = new PluginSpec(fooPack, { - config: 'foo', - }); - - expect(spec.getConfigSchemaProvider()).to.be('foo'); - }); - }); - - describe('#readConfigValue()', () => { - const spec = new PluginSpec(fooPack, { - configPrefix: 'foo.bar', - }); - - const config = { - get: sinon.stub(), - }; - - afterEach(() => config.get.resetHistory()); - - describe('key = "foo"', () => { - it('passes key as own array item', () => { - spec.readConfigValue(config, 'foo'); - sinon.assert.calledOnce(config.get); - sinon.assert.calledWithExactly(config.get, ['foo', 'bar', 'foo']); - }); - }); - - describe('key = "foo.bar"', () => { - it('passes key as two array items', () => { - spec.readConfigValue(config, 'foo.bar'); - sinon.assert.calledOnce(config.get); - sinon.assert.calledWithExactly(config.get, ['foo', 'bar', 'foo', 'bar']); - }); - }); - - describe('key = ["foo", "bar"]', () => { - it('merged keys into array', () => { - spec.readConfigValue(config, ['foo', 'bar']); - sinon.assert.calledOnce(config.get); - sinon.assert.calledWithExactly(config.get, ['foo', 'bar', 'foo', 'bar']); - }); - }); - }); - - describe('#getDeprecationsProvider()', () => { - it('returns spec.deprecations', () => { - const spec = new PluginSpec(fooPack, { - deprecations: 'foo', - }); - - expect(spec.getDeprecationsProvider()).to.be('foo'); - }); - }); - }); -}); diff --git a/src/legacy/plugin_discovery/plugin_spec/index.js b/src/legacy/plugin_discovery/plugin_spec/index.js deleted file mode 100644 index 671d311b152e2..0000000000000 --- a/src/legacy/plugin_discovery/plugin_spec/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { PluginSpec } from './plugin_spec'; diff --git a/src/legacy/plugin_discovery/plugin_spec/is_version_compatible.js b/src/legacy/plugin_discovery/plugin_spec/is_version_compatible.js deleted file mode 100644 index 6822c168f368d..0000000000000 --- a/src/legacy/plugin_discovery/plugin_spec/is_version_compatible.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { cleanVersion, versionSatisfies } from '../../utils/version'; - -export function isVersionCompatible(version, compatibleWith) { - // the special "kibana" version can be used to always be compatible, - // but is intentionally not supported by the plugin installer - if (version === 'kibana') { - return true; - } - - return versionSatisfies(cleanVersion(version), cleanVersion(compatibleWith)); -} diff --git a/src/legacy/plugin_discovery/plugin_spec/plugin_spec.js b/src/legacy/plugin_discovery/plugin_spec/plugin_spec.js deleted file mode 100644 index db1ec425f2ce5..0000000000000 --- a/src/legacy/plugin_discovery/plugin_spec/plugin_spec.js +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { resolve, basename, isAbsolute as isAbsolutePath } from 'path'; - -import { get, toPath } from 'lodash'; - -import { createInvalidPluginError } from '../errors'; -import { isVersionCompatible } from './is_version_compatible'; - -export class PluginSpec { - /** - * @param {PluginPack} pack The plugin pack that produced this spec - * @param {Object} opts the options for this plugin - * @param {String} [opts.id=pkg.name] the id for this plugin. - * @param {Object} [opts.uiExports] a mapping of UiExport types to - * UI modules or metadata about the UI module - * @param {Array} [opts.require] the other plugins that this plugin - * requires. These plugins must exist and be enabled for this plugin - * to function. The require'd plugins will also be initialized first, - * in order to make sure that dependencies provided by these plugins - * are available - * @param {String} [opts.version=pkg.version] the version of this plugin - * @param {Function} [opts.init] A function that will be called to initialize - * this plugin at the appropriate time. - * @param {Function} [opts.configPrefix=this.id] The prefix to use for - * configuration values in the main configuration service - * @param {Function} [opts.config] A function that produces a configuration - * schema using Joi, which is passed as its first argument. - * @param {String|False} [opts.publicDir=path + '/public'] the public - * directory for this plugin. The final directory must have the name "public", - * though it can be located somewhere besides the root of the plugin. Set - * this to false to disable exposure of a public directory - */ - constructor(pack, options) { - const { - id, - require, - version, - kibanaVersion, - uiExports, - uiCapabilities, - publicDir, - configPrefix, - config, - deprecations, - preInit, - init, - postInit, - isEnabled, - } = options; - - this._id = id; - this._pack = pack; - this._version = version; - this._kibanaVersion = kibanaVersion; - this._require = require; - - this._publicDir = publicDir; - this._uiExports = uiExports; - this._uiCapabilities = uiCapabilities; - - this._configPrefix = configPrefix; - this._configSchemaProvider = config; - this._configDeprecationsProvider = deprecations; - - this._isEnabled = isEnabled; - this._preInit = preInit; - this._init = init; - this._postInit = postInit; - - if (!this.getId()) { - throw createInvalidPluginError(this, 'Unable to determine plugin id'); - } - - if (!this.getVersion()) { - throw createInvalidPluginError(this, 'Unable to determine plugin version'); - } - - if (this.getRequiredPluginIds() !== undefined && !Array.isArray(this.getRequiredPluginIds())) { - throw createInvalidPluginError(this, '"plugin.require" must be an array of plugin ids'); - } - - if (this._publicDir) { - if (!isAbsolutePath(this._publicDir)) { - throw createInvalidPluginError(this, 'plugin.publicDir must be an absolute path'); - } - if (basename(this._publicDir) !== 'public') { - throw createInvalidPluginError( - this, - `publicDir for plugin ${this.getId()} must end with a "public" directory.` - ); - } - } - } - - getPack() { - return this._pack; - } - - getPkg() { - return this._pack.getPkg(); - } - - getPath() { - return this._pack.getPath(); - } - - getId() { - return this._id || this.getPkg().name; - } - - getVersion() { - return this._version || this.getPkg().version; - } - - isEnabled(config) { - if (!config || typeof config.get !== 'function' || typeof config.has !== 'function') { - throw new TypeError('PluginSpec#isEnabled() must be called with a config service'); - } - - if (this._isEnabled) { - return this._isEnabled(config); - } - - return Boolean(this.readConfigValue(config, 'enabled')); - } - - getExpectedKibanaVersion() { - // Plugins must specify their version, and by default that version should match - // the version of kibana down to the patch level. If these two versions need - // to diverge, they can specify a kibana.version in the package to indicate the - // version of kibana the plugin is intended to work with. - return ( - this._kibanaVersion || get(this.getPack().getPkg(), 'kibana.version') || this.getVersion() - ); - } - - isVersionCompatible(actualKibanaVersion) { - return isVersionCompatible(this.getExpectedKibanaVersion(), actualKibanaVersion); - } - - getRequiredPluginIds() { - return this._require; - } - - getPublicDir() { - if (this._publicDir === false) { - return null; - } - - if (!this._publicDir) { - return resolve(this.getPack().getPath(), 'public'); - } - - return this._publicDir; - } - - getExportSpecs() { - return this._uiExports; - } - - getUiCapabilitiesProvider() { - return this._uiCapabilities; - } - - getPreInitHandler() { - return this._preInit; - } - - getInitHandler() { - return this._init; - } - - getPostInitHandler() { - return this._postInit; - } - - getConfigPrefix() { - return this._configPrefix || this.getId(); - } - - getConfigSchemaProvider() { - return this._configSchemaProvider; - } - - readConfigValue(config, key) { - return config.get([...toPath(this.getConfigPrefix()), ...toPath(key)]); - } - - getDeprecationsProvider() { - return this._configDeprecationsProvider; - } -} diff --git a/src/legacy/plugin_discovery/plugin_spec/plugin_spec_options.d.ts b/src/legacy/plugin_discovery/plugin_spec/plugin_spec_options.d.ts deleted file mode 100644 index e1ed2f57375a4..0000000000000 --- a/src/legacy/plugin_discovery/plugin_spec/plugin_spec_options.d.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { Server } from '../../server/kbn_server'; -import { Capabilities } from '../../../core/server'; - -export type InitPluginFunction = (server: Server) => void; -export interface UiExports { - injectDefaultVars?: (server: Server) => { [key: string]: any }; -} - -export interface PluginSpecOptions { - id: string; - require?: string[]; - publicDir?: string; - uiExports?: UiExports; - uiCapabilities?: Capabilities; - init?: InitPluginFunction; - config?: any; -} diff --git a/src/legacy/plugin_discovery/types.ts b/src/legacy/plugin_discovery/types.ts deleted file mode 100644 index 700ca6fa68c95..0000000000000 --- a/src/legacy/plugin_discovery/types.ts +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Server } from '../server/kbn_server'; -import { Capabilities } from '../../core/server'; -import { AppCategory } from '../../core/types'; - -/** - * Usage - * - * ``` - * const apmOss: LegacyPlugin = (kibana) => { - * return new kibana.Plugin({ - * id: 'apm_oss', - * // ... - * }); - * }; - * ``` - */ -export type LegacyPluginInitializer = (kibana: LegacyPluginApi) => ArrayOrItem; - -export type ArrayOrItem = T | T[]; - -export interface LegacyPluginApi { - Plugin: new (options: Partial) => LegacyPluginSpec; -} - -export interface LegacyPluginOptions { - id: string; - require: string[]; - version: string; - kibanaVersion: 'kibana'; - uiExports: Partial<{ - app: Partial<{ - title: string; - category?: AppCategory; - description: string; - main: string; - icon: string; - euiIconType: string; - order: number; - listed: boolean; - }>; - apps: any; - hacks: string[]; - visualize: string[]; - devTools: string[]; - injectDefaultVars: (server: Server) => Record; - home: string[]; - mappings: any; - migrations: any; - visTypes: string[]; - embeddableActions?: string[]; - embeddableFactories?: string[]; - uiSettingDefaults?: Record; - interpreter: string | string[]; - }>; - uiCapabilities?: Capabilities; - publicDir: any; - configPrefix: any; - config: any; - deprecations: any; - preInit: any; - init: InitPluginFunction; - postInit: any; - isEnabled: boolean; -} - -export type InitPluginFunction = (server: Server) => void; - -export interface LegacyPluginSpec { - getPack(): any; - getPkg(): any; - getPath(): string; - getId(): string; - getVersion(): string; - isEnabled(config: any): boolean; - getExpectedKibanaVersion(): string; - isVersionCompatible(actualKibanaVersion: any): boolean; - getRequiredPluginIds(): string[]; - getPublicDir(): string | null; - getExportSpecs(): any; - getUiCapabilitiesProvider(): any; - getPreInitHandler(): any; - getInitHandler(): any; - getPostInitHandler(): any; - getConfigPrefix(): string; - getConfigSchemaProvider(): any; - readConfigValue(config: any, key: string): any; - getDeprecationsProvider(): any; -} diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js index f8736fb30f90e..a94766ef06926 100644 --- a/src/legacy/server/config/schema.js +++ b/src/legacy/server/config/schema.js @@ -131,6 +131,7 @@ export default () => }), }).default(), + // still used by the legacy i18n mixin plugins: Joi.object({ paths: Joi.array().items(Joi.string()).default([]), scanDirs: Joi.array().items(Joi.string()).default([]), @@ -146,71 +147,8 @@ export default () => status: Joi.object({ allowAnonymous: Joi.boolean().default(false), }).default(), - map: Joi.object({ - includeElasticMapsService: Joi.boolean().default(true), - proxyElasticMapsServiceInMaps: Joi.boolean().default(false), - tilemap: Joi.object({ - url: Joi.string(), - options: Joi.object({ - attribution: Joi.string(), - minZoom: Joi.number().min(0, 'Must be 0 or higher').default(0), - maxZoom: Joi.number().default(10), - tileSize: Joi.number(), - subdomains: Joi.array().items(Joi.string()).single(), - errorTileUrl: Joi.string().uri(), - tms: Joi.boolean(), - reuseTiles: Joi.boolean(), - bounds: Joi.array().items(Joi.array().items(Joi.number()).min(2).required()).min(2), - default: Joi.boolean(), - }).default({ - default: true, - }), - }).default(), - regionmap: Joi.object({ - includeElasticMapsService: Joi.boolean().default(true), - layers: Joi.array() - .items( - Joi.object({ - url: Joi.string(), - format: Joi.object({ - type: Joi.string().default('geojson'), - }).default({ - type: 'geojson', - }), - meta: Joi.object({ - feature_collection_path: Joi.string().default('data'), - }).default({ - feature_collection_path: 'data', - }), - attribution: Joi.string(), - name: Joi.string(), - fields: Joi.array().items( - Joi.object({ - name: Joi.string(), - description: Joi.string(), - }) - ), - }) - ) - .default([]), - }).default(), - manifestServiceUrl: Joi.string().default('').allow(''), - emsFileApiUrl: Joi.string().default('https://vector.maps.elastic.co'), - emsTileApiUrl: Joi.string().default('https://tiles.maps.elastic.co'), - emsLandingPageUrl: Joi.string().default('https://maps.elastic.co/v7.9'), - emsFontLibraryUrl: Joi.string().default( - 'https://tiles.maps.elastic.co/fonts/{fontstack}/{range}.pbf' - ), - emsTileLayerId: Joi.object({ - bright: Joi.string().default('road_map'), - desaturated: Joi.string().default('road_map_desaturated'), - dark: Joi.string().default('dark_map'), - }).default({ - bright: 'road_map', - desaturated: 'road_map_desaturated', - dark: 'dark_map', - }), - }).default(), + + map: HANDLED_IN_NEW_PLATFORM, i18n: Joi.object({ locale: Joi.string().default('en'), diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts index 3cfda0e0696bb..1718a9a8f55da 100644 --- a/src/legacy/server/kbn_server.d.ts +++ b/src/legacy/server/kbn_server.d.ts @@ -26,11 +26,10 @@ import { LoggerFactory, PackageInfo, LegacyServiceSetupDeps, - LegacyServiceDiscoverPlugins, } from '../../core/server'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { LegacyConfig, ILegacyInternals } from '../../core/server/legacy'; +import { LegacyConfig } from '../../core/server/legacy'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { UiPlugins } from '../../core/server/plugins'; @@ -58,9 +57,7 @@ export interface PluginsSetup { export interface KibanaCore { __internals: { - elasticsearch: LegacyServiceSetupDeps['core']['elasticsearch']; hapiServer: LegacyServiceSetupDeps['core']['http']['server']; - legacy: ILegacyInternals; rendering: LegacyServiceSetupDeps['core']['rendering']; uiPlugins: UiPlugins; }; @@ -90,31 +87,18 @@ export interface NewPlatform { stop: null; } -export type LegacyPlugins = Pick< - LegacyServiceDiscoverPlugins, - 'pluginSpecs' | 'disabledPluginSpecs' | 'uiExports' ->; - // eslint-disable-next-line import/no-default-export export default class KbnServer { public readonly newPlatform: NewPlatform; public server: Server; public inject: Server['inject']; - public pluginSpecs: any[]; - public uiBundles: any; - constructor( - settings: Record, - config: KibanaConfig, - core: KibanaCore, - legacyPlugins: LegacyPlugins - ); + constructor(settings: Record, config: KibanaConfig, core: KibanaCore); public ready(): Promise; public mixin(...fns: KbnMixinFunc[]): Promise; public listen(): Promise; public close(): Promise; - public afterPluginsInit(callback: () => void): void; public applyLoggingConfiguration(settings: any): void; public config: KibanaConfig; } diff --git a/src/legacy/server/kbn_server.js b/src/legacy/server/kbn_server.js index 107e5f6387833..e29563a7c6266 100644 --- a/src/legacy/server/kbn_server.js +++ b/src/legacy/server/kbn_server.js @@ -30,7 +30,6 @@ import { loggingMixin } from './logging'; import warningsMixin from './warnings'; import configCompleteMixin from './config/complete'; import { optimizeMixin } from '../../optimize'; -import * as Plugins from './plugins'; import { uiMixin } from '../ui'; import { i18nMixin } from './i18n'; @@ -47,9 +46,8 @@ export default class KbnServer { * @param {Record} settings * @param {KibanaConfig} config * @param {KibanaCore} core - * @param {LegacyPlugins} legacyPlugins */ - constructor(settings, config, core, legacyPlugins) { + constructor(settings, config, core) { this.name = pkg.name; this.version = pkg.version; this.build = pkg.build || false; @@ -74,14 +72,8 @@ export default class KbnServer { stop: null, }; - this.uiExports = legacyPlugins.uiExports; - this.pluginSpecs = legacyPlugins.pluginSpecs; - this.disabledPluginSpecs = legacyPlugins.disabledPluginSpecs; - this.ready = constant( this.mixin( - Plugins.waitForInitSetupMixin, - // Sets global HTTP behaviors httpMixin, @@ -93,22 +85,13 @@ export default class KbnServer { // scan translations dirs, register locale files and initialize i18n engine. i18nMixin, - // find plugins and set this.plugins and this.pluginSpecs - Plugins.scanMixin, - // tell the config we are done loading plugins configCompleteMixin, uiMixin, // setup routes that serve the @kbn/optimizer output - optimizeMixin, - - // initialize the plugins - Plugins.initializeMixin, - - // notify any deferred setup logic that plugins have initialized - Plugins.waitForInitResolveMixin + optimizeMixin ) ); diff --git a/src/legacy/server/plugins/index.js b/src/legacy/server/plugins/index.js deleted file mode 100644 index 1511b63b519ae..0000000000000 --- a/src/legacy/server/plugins/index.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { scanMixin } from './scan_mixin'; -export { initializeMixin } from './initialize_mixin'; -export { waitForInitSetupMixin, waitForInitResolveMixin } from './wait_for_plugins_init'; diff --git a/src/legacy/server/plugins/initialize_mixin.js b/src/legacy/server/plugins/initialize_mixin.js deleted file mode 100644 index ccf4cd1c1a404..0000000000000 --- a/src/legacy/server/plugins/initialize_mixin.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { callPluginHook } from './lib'; - -/** - * KbnServer mixin that initializes all plugins found in ./scan mixin - * @param {KbnServer} kbnServer - * @param {Hapi.Server} server - * @param {Config} config - * @return {Promise} - */ -export async function initializeMixin(kbnServer, server, config) { - if (!config.get('plugins.initialize')) { - server.log(['info'], 'Plugin initialization disabled.'); - return; - } - - async function callHookOnPlugins(hookName) { - const { plugins } = kbnServer; - const ids = plugins.map((p) => p.id); - - for (const id of ids) { - await callPluginHook(hookName, plugins, id, []); - } - } - - await callHookOnPlugins('preInit'); - await callHookOnPlugins('init'); - await callHookOnPlugins('postInit'); -} diff --git a/src/legacy/server/plugins/lib/call_plugin_hook.js b/src/legacy/server/plugins/lib/call_plugin_hook.js deleted file mode 100644 index b665869f5d25f..0000000000000 --- a/src/legacy/server/plugins/lib/call_plugin_hook.js +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { last } from 'lodash'; - -export async function callPluginHook(hookName, plugins, id, history) { - const plugin = plugins.find((plugin) => plugin.id === id); - - // make sure this is a valid plugin id - if (!plugin) { - if (history.length) { - throw new Error(`Unmet requirement "${id}" for plugin "${last(history)}"`); - } else { - throw new Error(`Unknown plugin "${id}"`); - } - } - - const circleStart = history.indexOf(id); - const path = [...history, id]; - - // make sure we are not trying to load a dependency within itself - if (circleStart > -1) { - const circle = path.slice(circleStart); - throw new Error(`circular dependency found: "${circle.join(' -> ')}"`); - } - - // call hook on all dependencies - for (const req of plugin.requiredIds) { - await callPluginHook(hookName, plugins, req, path); - } - - // call hook on this plugin - await plugin[hookName](); -} diff --git a/src/legacy/server/plugins/lib/call_plugin_hook.test.js b/src/legacy/server/plugins/lib/call_plugin_hook.test.js deleted file mode 100644 index 30dc2d91a9ab2..0000000000000 --- a/src/legacy/server/plugins/lib/call_plugin_hook.test.js +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import sinon from 'sinon'; -import { callPluginHook } from './call_plugin_hook'; - -describe('server/plugins/callPluginHook', () => { - it('should call in correct order based on requirements', async () => { - const plugins = [ - { - id: 'foo', - init: sinon.spy(), - preInit: sinon.spy(), - requiredIds: ['bar', 'baz'], - }, - { - id: 'bar', - init: sinon.spy(), - preInit: sinon.spy(), - requiredIds: [], - }, - { - id: 'baz', - init: sinon.spy(), - preInit: sinon.spy(), - requiredIds: ['bar'], - }, - ]; - - await callPluginHook('init', plugins, 'foo', []); - const [foo, bar, baz] = plugins; - sinon.assert.calledOnce(foo.init); - sinon.assert.calledTwice(bar.init); - sinon.assert.calledOnce(baz.init); - sinon.assert.callOrder(bar.init, baz.init, foo.init); - }); - - it('throws meaningful error when required plugin is missing', async () => { - const plugins = [ - { - id: 'foo', - init: sinon.spy(), - preInit: sinon.spy(), - requiredIds: ['bar'], - }, - ]; - - try { - await callPluginHook('init', plugins, 'foo', []); - throw new Error('expected callPluginHook to throw'); - } catch (error) { - expect(error.message).toContain('"bar" for plugin "foo"'); - } - }); - - it('throws meaningful error when dependencies are circular', async () => { - const plugins = [ - { - id: 'foo', - init: sinon.spy(), - preInit: sinon.spy(), - requiredIds: ['bar'], - }, - { - id: 'bar', - init: sinon.spy(), - preInit: sinon.spy(), - requiredIds: ['baz'], - }, - { - id: 'baz', - init: sinon.spy(), - preInit: sinon.spy(), - requiredIds: ['foo'], - }, - ]; - - try { - await callPluginHook('init', plugins, 'foo', []); - throw new Error('expected callPluginHook to throw'); - } catch (error) { - expect(error.message).toContain('foo -> bar -> baz -> foo'); - } - }); -}); diff --git a/src/legacy/server/plugins/lib/index.js b/src/legacy/server/plugins/lib/index.js deleted file mode 100644 index 2329d24498b6b..0000000000000 --- a/src/legacy/server/plugins/lib/index.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { callPluginHook } from './call_plugin_hook'; -export { Plugin } from './plugin'; diff --git a/src/legacy/server/plugins/lib/plugin.js b/src/legacy/server/plugins/lib/plugin.js deleted file mode 100644 index 48389061199ff..0000000000000 --- a/src/legacy/server/plugins/lib/plugin.js +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { once } from 'lodash'; - -/** - * The server plugin class, used to extend the server - * and add custom behavior. A "scoped" plugin class is - * created by the PluginApi class and provided to plugin - * providers that automatically binds all but the `opts` - * arguments. - * - * @class Plugin - * @param {KbnServer} kbnServer - the KbnServer this plugin - * belongs to. - * @param {PluginDefinition} def - * @param {PluginSpec} spec - */ -export class Plugin { - constructor(kbnServer, spec) { - this.kbnServer = kbnServer; - this.spec = spec; - this.pkg = spec.getPkg(); - this.path = spec.getPath(); - this.id = spec.getId(); - this.version = spec.getVersion(); - this.requiredIds = spec.getRequiredPluginIds() || []; - this.externalPreInit = spec.getPreInitHandler(); - this.externalInit = spec.getInitHandler(); - this.externalPostInit = spec.getPostInitHandler(); - this.enabled = spec.isEnabled(kbnServer.config); - this.configPrefix = spec.getConfigPrefix(); - this.publicDir = spec.getPublicDir(); - - this.preInit = once(this.preInit); - this.init = once(this.init); - this.postInit = once(this.postInit); - } - - async preInit() { - if (this.externalPreInit) { - return await this.externalPreInit(this.kbnServer.server); - } - } - - async init() { - const { id, version, kbnServer, configPrefix } = this; - const { config } = kbnServer; - - // setup the hapi register function and get on with it - const register = async (server, options) => { - this._server = server; - this._options = options; - - server.logWithMetadata(['plugins', 'debug'], `Initializing plugin ${this.toString()}`, { - plugin: this, - }); - - if (this.publicDir) { - server.newPlatform.__internals.http.registerStaticDir( - `/plugins/${id}/{path*}`, - this.publicDir - ); - } - - if (this.externalInit) { - await this.externalInit(server, options); - } - }; - - await kbnServer.server.register({ - plugin: { register, name: id, version }, - options: config.has(configPrefix) ? config.get(configPrefix) : null, - }); - } - - async postInit() { - if (this.externalPostInit) { - return await this.externalPostInit(this.kbnServer.server); - } - } - - getServer() { - return this._server; - } - - getOptions() { - return this._options; - } - - toJSON() { - return this.pkg; - } - - toString() { - return `${this.id}@${this.version}`; - } -} diff --git a/src/legacy/server/plugins/scan_mixin.js b/src/legacy/server/plugins/scan_mixin.js deleted file mode 100644 index 89ebaf920d9d1..0000000000000 --- a/src/legacy/server/plugins/scan_mixin.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { Plugin } from './lib'; - -export async function scanMixin(kbnServer) { - kbnServer.plugins = kbnServer.pluginSpecs.map((spec) => new Plugin(kbnServer, spec)); -} diff --git a/src/legacy/server/plugins/wait_for_plugins_init.js b/src/legacy/server/plugins/wait_for_plugins_init.js deleted file mode 100644 index 144eb5ef803cc..0000000000000 --- a/src/legacy/server/plugins/wait_for_plugins_init.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * Tracks the individual queue for each kbnServer, rather than attaching - * it to the kbnServer object via a property or something - * @type {WeakMap} - */ -const queues = new WeakMap(); - -export function waitForInitSetupMixin(kbnServer) { - queues.set(kbnServer, []); - - kbnServer.afterPluginsInit = function (callback) { - const queue = queues.get(kbnServer); - - if (!queue) { - throw new Error( - 'Plugins have already initialized. Only use this method for setup logic that must wait for plugins to initialize.' - ); - } - - queue.push(callback); - }; -} - -export async function waitForInitResolveMixin(kbnServer, server, config) { - const queue = queues.get(kbnServer); - queues.set(kbnServer, null); - - // only actually call the callbacks if we are really initializing - if (config.get('plugins.initialize')) { - for (const cb of queue) { - await cb(); - } - } -} diff --git a/src/legacy/types.ts b/src/legacy/types.ts deleted file mode 100644 index 43c9ac79538b1..0000000000000 --- a/src/legacy/types.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export * from './plugin_discovery/types'; diff --git a/src/legacy/ui/__tests__/fixtures/plugin_async_foo/index.js b/src/legacy/ui/__tests__/fixtures/plugin_async_foo/index.js deleted file mode 100644 index afe618c6d3d9c..0000000000000 --- a/src/legacy/ui/__tests__/fixtures/plugin_async_foo/index.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import Bluebird from 'bluebird'; - -export default (kibana) => - new kibana.Plugin({ - config(Joi) { - return Joi.object() - .keys({ - enabled: Joi.boolean().default(true), - delay: Joi.number().required(), - shared: Joi.string(), - }) - .default(); - }, - - uiExports: { - async injectDefaultVars(server, options) { - await Bluebird.delay(options.delay); - return { shared: options.shared }; - }, - }, - }); diff --git a/src/legacy/ui/__tests__/fixtures/plugin_async_foo/package.json b/src/legacy/ui/__tests__/fixtures/plugin_async_foo/package.json deleted file mode 100644 index fc1c8d8088f1b..0000000000000 --- a/src/legacy/ui/__tests__/fixtures/plugin_async_foo/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "plugin_async_foo", - "version": "kibana" -} diff --git a/src/legacy/ui/__tests__/fixtures/plugin_bar/index.js b/src/legacy/ui/__tests__/fixtures/plugin_bar/index.js deleted file mode 100644 index 975a1dc7c92e7..0000000000000 --- a/src/legacy/ui/__tests__/fixtures/plugin_bar/index.js +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export default (kibana) => - new kibana.Plugin({ - config(Joi) { - return Joi.object() - .keys({ - enabled: Joi.boolean().default(true), - shared: Joi.string(), - }) - .default(); - }, - - uiExports: { - injectDefaultVars(server, options) { - return { shared: options.shared }; - }, - }, - }); diff --git a/src/legacy/ui/__tests__/fixtures/plugin_bar/package.json b/src/legacy/ui/__tests__/fixtures/plugin_bar/package.json deleted file mode 100644 index f79b807990dca..0000000000000 --- a/src/legacy/ui/__tests__/fixtures/plugin_bar/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "plugin_bar", - "version": "kibana" -} diff --git a/src/legacy/ui/__tests__/fixtures/plugin_foo/index.js b/src/legacy/ui/__tests__/fixtures/plugin_foo/index.js deleted file mode 100644 index 975a1dc7c92e7..0000000000000 --- a/src/legacy/ui/__tests__/fixtures/plugin_foo/index.js +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export default (kibana) => - new kibana.Plugin({ - config(Joi) { - return Joi.object() - .keys({ - enabled: Joi.boolean().default(true), - shared: Joi.string(), - }) - .default(); - }, - - uiExports: { - injectDefaultVars(server, options) { - return { shared: options.shared }; - }, - }, - }); diff --git a/src/legacy/ui/__tests__/fixtures/plugin_foo/package.json b/src/legacy/ui/__tests__/fixtures/plugin_foo/package.json deleted file mode 100644 index c1b7ddd35c9a2..0000000000000 --- a/src/legacy/ui/__tests__/fixtures/plugin_foo/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "plugin_foo", - "version": "kibana" -} diff --git a/src/legacy/ui/__tests__/fixtures/test_app/index.js b/src/legacy/ui/__tests__/fixtures/test_app/index.js deleted file mode 100644 index 3eddefd618ce0..0000000000000 --- a/src/legacy/ui/__tests__/fixtures/test_app/index.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export default (kibana) => - new kibana.Plugin({ - uiExports: { - app: { - name: 'test_app', - main: 'plugins/test_app/index.js', - }, - - injectDefaultVars() { - return { - from_defaults: true, - }; - }, - }, - init(server) { - server.injectUiAppVars('test_app', () => ({ - from_test_app: true, - })); - }, - }); diff --git a/src/legacy/ui/__tests__/fixtures/test_app/package.json b/src/legacy/ui/__tests__/fixtures/test_app/package.json deleted file mode 100644 index 3aeb029e4f4cc..0000000000000 --- a/src/legacy/ui/__tests__/fixtures/test_app/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "test_app", - "version": "kibana" -} diff --git a/src/legacy/ui/index.js b/src/legacy/ui/index.js index 05373fa5d1964..5c06cb4677347 100644 --- a/src/legacy/ui/index.js +++ b/src/legacy/ui/index.js @@ -18,4 +18,3 @@ */ export { uiMixin } from './ui_mixin'; -export { collectUiExports } from './ui_exports'; diff --git a/src/legacy/ui/ui_exports/README.md b/src/legacy/ui/ui_exports/README.md deleted file mode 100644 index 7fb117b1c25b9..0000000000000 --- a/src/legacy/ui/ui_exports/README.md +++ /dev/null @@ -1,95 +0,0 @@ -# UI Exports - -When defining a Plugin, the `uiExports` key can be used to define a map of export types to values that will be used to configure the UI system. A common use for `uiExports` is `uiExports.app`, which defines the configuration of a [`UiApp`][UiApp] and teaches the UI System how to render, bundle and tell the user about an application. - - -## `collectUiExports(pluginSpecs): { [type: string]: any }` - -This function produces the object commonly found at `kbnServer.uiExports`. This object is created by calling `collectPluginExports()` with a standard set of export type reducers and defaults for the UI System. - -### export type reducers - -The [`ui_export_types` module][UiExportTypes] defines the reducer used for each uiExports key (or `type`). The name of every export in [./ui_export_types/index.js][UiExportTypes] is a key that plugins can define in their `uiExports` specification and the value of those exports are reducers that `collectPluginExports()` will call to produce the merged result of all export specs. - -### example - UiApps - -Plugin authors can define a new UiApp in their plugin specification like so: - -```js -// a single app export -export default function (kibana) { - return new kibana.Plugin({ - //... - uiExports: { - app: { - // uiApp spec options go here - } - } - }) -} - -// apps can also export multiple apps -export default function (kibana) { - return new kibana.Plugin({ - //... - uiExports: { - apps: [ - { /* uiApp spec options */ }, - { /* second uiApp spec options */ }, - ] - } - }) -} -``` - -To handle this export type, the [ui_export_types][UiExportTypes] module exports two reducers, one named `app` and the other `apps`. - -```js -export const app = ... -export const apps = ... -``` - -These reducers are defined in [`ui_export_types/ui_apps`][UiAppExportType] and have the exact same definition: - -```js -// `wrap()` produces a reducer by wrapping a base reducer with modifiers. -// All but the last argument are modifiers that take a reducer and return -// an alternate reducer to use in it's place. -// -// Most wrappers call their target reducer with slightly different -// arguments. This allows composing standard reducer modifications for -// reuse, consistency, and easy reference (once you get the hang of it). -wrap( - // calls the next reducer with the `type` set to `uiAppSpecs`, ignoring - // the key the plugin author used to define this spec ("app" or "apps" - // in this example) - alias('uiAppSpecs'), - - // calls the next reducer with the `spec` set to the result of calling - // `applySpecDefaults(spec, type, pluginSpec)` which merges some defaults - // from the `PluginSpec` because we want uiAppSpecs to be useful individually - mapSpec(applySpecDefaults), - - // writes this spec to `acc[type]` (`acc.uiAppSpecs` in this example since - // the type was set to `uiAppSpecs` by `alias()`). It does this by concatenating - // the current value and the spec into an array. If either item is already - // an array its items are added to the result individually. If either item - // is undefined it is ignored. - // - // NOTE: since flatConcatAtType is last it isn't a wrapper, it's - // just a normal reducer - flatConcatAtType -) -``` - -This reducer format was chosen so that it will be easier to look back at these reducers and see that `app` and `apps` export specs are written to `kbnServer.uiExports.uiAppSpecs`, with defaults applied, in an array. - -### defaults - -The [`ui_exports/ui_export_defaults`][UiExportDefaults] module defines the default shape of the uiExports object produced by `collectUiExports()`. The defaults generally describe the `uiExports` from the UI System itself, like default visTypes and such. - -[UiExportDefaults]: ./ui_export_defaults.js "uiExport defaults definition" -[UiExportTypes]: ./ui_export_types/index.js "Index of default ui_export_types module" -[UiAppExportType]: ./ui_export_types/ui_apps.js "UiApp extension type definition" -[PluginSpec]: ../../plugin_discovery/plugin_spec/plugin_spec.js "PluginSpec class definition" -[PluginDiscovery]: '../../plugin_discovery' "plugin_discovery module" \ No newline at end of file diff --git a/src/legacy/ui/ui_exports/collect_ui_exports.ts b/src/legacy/ui/ui_exports/collect_ui_exports.ts deleted file mode 100644 index edb2a11dc0527..0000000000000 --- a/src/legacy/ui/ui_exports/collect_ui_exports.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { LegacyUiExports } from '../../../core/server'; - -// @ts-ignore -import { UI_EXPORT_DEFAULTS } from './ui_export_defaults'; -// @ts-ignore -import * as uiExportTypeReducers from './ui_export_types'; -// @ts-ignore -import { reduceExportSpecs } from '../../plugin_discovery'; - -export function collectUiExports(pluginSpecs: unknown[]): LegacyUiExports { - return reduceExportSpecs(pluginSpecs, uiExportTypeReducers, UI_EXPORT_DEFAULTS); -} diff --git a/src/legacy/ui/ui_exports/index.js b/src/legacy/ui/ui_exports/index.js deleted file mode 100644 index 56db698dc7b03..0000000000000 --- a/src/legacy/ui/ui_exports/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { collectUiExports } from './collect_ui_exports'; diff --git a/src/legacy/ui/ui_exports/ui_export_defaults.js b/src/legacy/ui/ui_exports/ui_export_defaults.js deleted file mode 100644 index 227954155ce88..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_defaults.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export const UI_EXPORT_DEFAULTS = {}; diff --git a/src/legacy/ui/ui_exports/ui_export_types/index.js b/src/legacy/ui/ui_exports/ui_export_types/index.js deleted file mode 100644 index 9ff6a53f4afb9..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/index.js +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { injectDefaultVars, replaceInjectedVars } from './modify_injected_vars'; - -export { - mappings, - migrations, - savedObjectSchemas, - savedObjectsManagement, - validations, -} from './saved_object'; - -export { taskDefinitions } from './task_definitions'; - -export { link, links } from './ui_nav_links'; - -export { uiSettingDefaults } from './ui_settings'; - -export { unknown } from './unknown'; diff --git a/src/legacy/ui/ui_exports/ui_export_types/modify_injected_vars.js b/src/legacy/ui/ui_exports/ui_export_types/modify_injected_vars.js deleted file mode 100644 index 4bb9f350bd959..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/modify_injected_vars.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { flatConcatAtType } from './reduce'; -import { wrap, alias, mapSpec } from './modify_reduce'; - -export const replaceInjectedVars = wrap(alias('injectedVarsReplacers'), flatConcatAtType); - -export const injectDefaultVars = wrap( - alias('defaultInjectedVarProviders'), - mapSpec((spec, type, pluginSpec) => ({ - pluginSpec, - fn: spec, - })), - flatConcatAtType -); diff --git a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/alias.js b/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/alias.js deleted file mode 100644 index a894e59a03c81..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/alias.js +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * Creates a reducer wrapper which, when called with a reducer, creates a new - * reducer that replaces the `type` value with `newType` before delegating to - * the wrapped reducer - * @param {String} newType - * @return {Function} - */ -export const alias = (newType) => (next) => (acc, spec, type, pluginSpec) => - next(acc, spec, newType, pluginSpec); diff --git a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/debug.js b/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/debug.js deleted file mode 100644 index c40bca59fe14c..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/debug.js +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { mapSpec } from './map_spec'; - -/** - * Reducer wrapper which, replaces the `spec` with the details about the definition - * of that spec - * @type {Function} - */ -export const debug = mapSpec((spec, type, pluginSpec) => ({ - spec, - type, - pluginSpec, -})); diff --git a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/index.js b/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/index.js deleted file mode 100644 index 54c81fefdd08a..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/index.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { alias } from './alias'; -export { debug } from './debug'; -export { mapSpec } from './map_spec'; -export { wrap } from './wrap'; -export { uniqueKeys } from './unique_keys'; diff --git a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/map_spec.js b/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/map_spec.js deleted file mode 100644 index 5970c45e7445e..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/map_spec.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * Creates a reducer wrapper which, when called with a reducer, creates a new - * reducer that replaces the `specs` value with the result of calling - * `mapFn(spec, type, pluginSpec)` before delegating to the wrapped - * reducer - * @param {Function} mapFn receives `(specs, type, pluginSpec)` - * @return {Function} - */ -export const mapSpec = (mapFn) => (next) => (acc, spec, type, pluginSpec) => - next(acc, mapFn(spec, type, pluginSpec), type, pluginSpec); diff --git a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/unique_keys.js b/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/unique_keys.js deleted file mode 100644 index dedcd057b09e3..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/unique_keys.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const pluginId = (pluginSpec) => (pluginSpec.id ? pluginSpec.id() : pluginSpec.getId()); - -export const uniqueKeys = (sourceType) => (next) => (acc, spec, type, pluginSpec) => { - const duplicates = Object.keys(spec).filter((key) => acc[type] && acc[type].hasOwnProperty(key)); - - if (duplicates.length) { - throw new Error( - `${pluginId(pluginSpec)} defined duplicate ${sourceType || type} values: ${duplicates}` - ); - } - - return next(acc, spec, type, pluginSpec); -}; diff --git a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/wrap.js b/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/wrap.js deleted file mode 100644 index f84d83ed7c845..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/modify_reduce/wrap.js +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * Wrap a function with any number of wrappers. Wrappers - * are functions that take a reducer and return a reducer - * that should be called in its place. The wrappers will - * be called in reverse order for setup and then in the - * order they are defined when the resulting reducer is - * executed. - * - * const reducer = wrap( - * next => (acc) => acc[1] = 'a', - * next => (acc) => acc[1] = 'b', - * next => (acc) => acc[1] = 'c' - * ) - * - * reducer('foo') //=> 'fco' - * - * @param {Function} ...wrappers - * @param {Function} reducer - * @return {Function} - */ -export function wrap(...args) { - const reducer = args[args.length - 1]; - const wrappers = args.slice(0, -1); - - return wrappers.reverse().reduce((acc, wrapper) => wrapper(acc), reducer); -} diff --git a/src/legacy/ui/ui_exports/ui_export_types/reduce/flat_concat_at_type.js b/src/legacy/ui/ui_exports/ui_export_types/reduce/flat_concat_at_type.js deleted file mode 100644 index 5fcbcac463392..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/reduce/flat_concat_at_type.js +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { createTypeReducer, flatConcat } from './lib'; - -/** - * Reducer that merges two values concatenating all values - * into a flattened array - * @param {Any} [initial] - * @return {Function} - */ -export const flatConcatAtType = createTypeReducer(flatConcat); diff --git a/src/legacy/ui/ui_exports/ui_export_types/reduce/index.js b/src/legacy/ui/ui_exports/ui_export_types/reduce/index.js deleted file mode 100644 index 7dc1ba60fb3cb..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/reduce/index.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { mergeAtType } from './merge_at_type'; -export { flatConcatValuesAtType } from './flat_concat_values_at_type'; -export { flatConcatAtType } from './flat_concat_at_type'; diff --git a/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/create_type_reducer.js b/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/create_type_reducer.js deleted file mode 100644 index bf4793c208308..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/create_type_reducer.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * Creates a reducer that reduces the values within `acc[type]` by calling - * reducer with signature: - * - * reducer(acc[type], spec, type, pluginSpec) - * - * @param {Function} reducer - * @return {Function} - */ -export const createTypeReducer = (reducer) => (acc, spec, type, pluginSpec) => ({ - ...acc, - [type]: reducer(acc[type], spec, type, pluginSpec), -}); diff --git a/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/flat_concat.js b/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/flat_concat.js deleted file mode 100644 index 1337c8a85d5b4..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/flat_concat.js +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * Concatenate two values into a single array, ignoring either - * value if it is undefined and flattening the value if it is an array - * @param {Array|T} a - * @param {Array} b - * @return {Array} - */ -export const flatConcat = (a, b) => [].concat(a === undefined ? [] : a, b === undefined ? [] : b); diff --git a/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/index.js b/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/index.js deleted file mode 100644 index e4281caebe245..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/index.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { flatConcat } from './flat_concat'; -export { mergeWith } from './merge_with'; -export { createTypeReducer } from './create_type_reducer'; diff --git a/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/merge_with.js b/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/merge_with.js deleted file mode 100644 index 6c7d31e6fd74d..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/reduce/lib/merge_with.js +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const uniqueConcat = (arrayA, arrayB) => - arrayB.reduce((acc, key) => (acc.includes(key) ? acc : acc.concat(key)), arrayA); - -/** - * Assign the keys from both objA and objB to target after passing the - * current and new value through merge as `(target[key], source[key])` - * @param {Object} objA - * @param {Object} objB - * @param {Function} merge - * @return {Object} target - */ -export function mergeWith(objA, objB, merge) { - const target = {}; - const keys = uniqueConcat(Object.keys(objA), Object.keys(objB)); - for (const key of keys) { - target[key] = merge(objA[key], objB[key]); - } - return target; -} diff --git a/src/legacy/ui/ui_exports/ui_export_types/reduce/merge_at_type.js b/src/legacy/ui/ui_exports/ui_export_types/reduce/merge_at_type.js deleted file mode 100644 index 4f5a501253851..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/reduce/merge_at_type.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { createTypeReducer } from './lib'; - -export const mergeAtType = createTypeReducer((a, b) => ({ - ...a, - ...b, -})); diff --git a/src/legacy/ui/ui_exports/ui_export_types/saved_object.js b/src/legacy/ui/ui_exports/ui_export_types/saved_object.js deleted file mode 100644 index be6898d3e642c..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/saved_object.js +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { flatConcatAtType, mergeAtType } from './reduce'; -import { alias, mapSpec, uniqueKeys, wrap } from './modify_reduce'; - -// mapping types -export const mappings = wrap( - alias('savedObjectMappings'), - mapSpec((spec, type, pluginSpec) => ({ - pluginId: pluginSpec.getId(), - properties: spec, - })), - flatConcatAtType -); - -const pluginId = (pluginSpec) => (pluginSpec.id ? pluginSpec.id() : pluginSpec.getId()); - -// Combines the `migrations` property of each plugin, -// ensuring that properties are unique across plugins -// and has migrations defined where the mappings are defined. -// See saved_objects/migrations for more details. -export const migrations = wrap( - alias('savedObjectMigrations'), - (next) => (acc, spec, type, pluginSpec) => { - const mappings = pluginSpec.getExportSpecs().mappings || {}; - const invalidMigrationTypes = Object.keys(spec).filter((type) => !mappings[type]); - if (invalidMigrationTypes.length) { - throw new Error( - 'Migrations and mappings must be defined together in the uiExports of a single plugin. ' + - `${pluginId(pluginSpec)} defines migrations for types ${invalidMigrationTypes.join( - ', ' - )} but does not define their mappings.` - ); - } - return next(acc, spec, type, pluginSpec); - }, - uniqueKeys(), - mergeAtType -); - -export const savedObjectSchemas = wrap(uniqueKeys(), mergeAtType); - -export const savedObjectsManagement = wrap(uniqueKeys(), mergeAtType); - -// Combines the `validations` property of each plugin, -// ensuring that properties are unique across plugins. -// See saved_objects/validation for more details. -export const validations = wrap(alias('savedObjectValidations'), uniqueKeys(), mergeAtType); diff --git a/src/legacy/ui/ui_exports/ui_export_types/task_definitions.js b/src/legacy/ui/ui_exports/ui_export_types/task_definitions.js deleted file mode 100644 index 8a0ed85d86f3e..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/task_definitions.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { mergeAtType } from './reduce'; -import { alias, wrap, uniqueKeys } from './modify_reduce'; - -// How plugins define tasks that the task manager can run. -export const taskDefinitions = wrap(alias('taskDefinitions'), uniqueKeys(), mergeAtType); diff --git a/src/legacy/ui/ui_exports/ui_export_types/ui_nav_links.js b/src/legacy/ui/ui_exports/ui_export_types/ui_nav_links.js deleted file mode 100644 index 34aff7463a249..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/ui_nav_links.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { flatConcatAtType } from './reduce'; -import { wrap, alias } from './modify_reduce'; - -export const links = wrap(alias('navLinkSpecs'), flatConcatAtType); -export const link = wrap(alias('navLinkSpecs'), flatConcatAtType); diff --git a/src/legacy/ui/ui_exports/ui_export_types/ui_settings.js b/src/legacy/ui/ui_exports/ui_export_types/ui_settings.js deleted file mode 100644 index 8d88490579c21..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/ui_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { mergeAtType } from './reduce'; -import { wrap, uniqueKeys } from './modify_reduce'; - -export const uiSettingDefaults = wrap(uniqueKeys(), mergeAtType); diff --git a/src/legacy/ui/ui_exports/ui_export_types/unknown.js b/src/legacy/ui/ui_exports/ui_export_types/unknown.js deleted file mode 100644 index a12a514d2e6bf..0000000000000 --- a/src/legacy/ui/ui_exports/ui_export_types/unknown.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { flatConcatAtType } from './reduce'; -import { wrap, alias, debug } from './modify_reduce'; - -export const unknown = wrap(debug, alias('unknown'), flatConcatAtType); diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index e3b7c1e0c3ff9..2983dbbc28667 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -67,115 +67,108 @@ export function uiRenderMixin(kbnServer, server, config) { }, }); - // register the bootstrap.js route after plugins are initialized so that we can - // detect if any default auth strategies were registered - kbnServer.afterPluginsInit(() => { - const authEnabled = !!server.auth.settings.default; - - server.route({ - path: '/bootstrap.js', - method: 'GET', - config: { - tags: ['api'], - auth: authEnabled ? { mode: 'try' } : false, - }, - async handler(request, h) { - const soClient = kbnServer.newPlatform.start.core.savedObjects.getScopedClient( - KibanaRequest.from(request) - ); - const uiSettings = kbnServer.newPlatform.start.core.uiSettings.asScopedToClient(soClient); - - const darkMode = - !authEnabled || request.auth.isAuthenticated - ? await uiSettings.get('theme:darkMode') - : false; - - const themeVersion = - !authEnabled || request.auth.isAuthenticated - ? await uiSettings.get('theme:version') - : 'v7'; - - const themeTag = `${themeVersion === 'v7' ? 'v7' : 'v8'}${darkMode ? 'dark' : 'light'}`; - - const buildHash = server.newPlatform.env.packageInfo.buildNum; - const basePath = config.get('server.basePath'); - - const regularBundlePath = `${basePath}/${buildHash}/bundles`; - - const styleSheetPaths = [ - `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.baseCssDistFilename}`, - ...(darkMode - ? [ - themeVersion === 'v7' - ? `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.darkCssDistFilename}` - : `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.darkV8CssDistFilename}`, - `${basePath}/node_modules/@kbn/ui-framework/dist/kui_dark.css`, - `${basePath}/ui/legacy_dark_theme.css`, - ] - : [ - themeVersion === 'v7' - ? `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.lightCssDistFilename}` - : `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.lightV8CssDistFilename}`, - `${basePath}/node_modules/@kbn/ui-framework/dist/kui_light.css`, - `${basePath}/ui/legacy_light_theme.css`, - ]), - ]; - - const kpUiPlugins = kbnServer.newPlatform.__internals.uiPlugins; - const kpPluginPublicPaths = new Map(); - const kpPluginBundlePaths = new Set(); - - // recursively iterate over the kpUiPlugin ids and their required bundles - // to populate kpPluginPublicPaths and kpPluginBundlePaths - (function readKpPlugins(ids) { - for (const id of ids) { - if (kpPluginPublicPaths.has(id)) { - continue; - } - - kpPluginPublicPaths.set(id, `${regularBundlePath}/plugin/${id}/`); - kpPluginBundlePaths.add(`${regularBundlePath}/plugin/${id}/${id}.plugin.js`); - readKpPlugins(kpUiPlugins.internal.get(id).requiredBundles); + const authEnabled = !!server.auth.settings.default; + server.route({ + path: '/bootstrap.js', + method: 'GET', + config: { + tags: ['api'], + auth: authEnabled ? { mode: 'try' } : false, + }, + async handler(request, h) { + const soClient = kbnServer.newPlatform.start.core.savedObjects.getScopedClient( + KibanaRequest.from(request) + ); + const uiSettings = kbnServer.newPlatform.start.core.uiSettings.asScopedToClient(soClient); + + const darkMode = + !authEnabled || request.auth.isAuthenticated + ? await uiSettings.get('theme:darkMode') + : false; + + const themeVersion = + !authEnabled || request.auth.isAuthenticated ? await uiSettings.get('theme:version') : 'v7'; + + const themeTag = `${themeVersion === 'v7' ? 'v7' : 'v8'}${darkMode ? 'dark' : 'light'}`; + + const buildHash = server.newPlatform.env.packageInfo.buildNum; + const basePath = config.get('server.basePath'); + + const regularBundlePath = `${basePath}/${buildHash}/bundles`; + + const styleSheetPaths = [ + `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.baseCssDistFilename}`, + ...(darkMode + ? [ + themeVersion === 'v7' + ? `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.darkCssDistFilename}` + : `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.darkV8CssDistFilename}`, + `${basePath}/node_modules/@kbn/ui-framework/dist/kui_dark.css`, + `${basePath}/ui/legacy_dark_theme.css`, + ] + : [ + themeVersion === 'v7' + ? `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.lightCssDistFilename}` + : `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.lightV8CssDistFilename}`, + `${basePath}/node_modules/@kbn/ui-framework/dist/kui_light.css`, + `${basePath}/ui/legacy_light_theme.css`, + ]), + ]; + + const kpUiPlugins = kbnServer.newPlatform.__internals.uiPlugins; + const kpPluginPublicPaths = new Map(); + const kpPluginBundlePaths = new Set(); + + // recursively iterate over the kpUiPlugin ids and their required bundles + // to populate kpPluginPublicPaths and kpPluginBundlePaths + (function readKpPlugins(ids) { + for (const id of ids) { + if (kpPluginPublicPaths.has(id)) { + continue; } - })(kpUiPlugins.public.keys()); - - const jsDependencyPaths = [ - ...UiSharedDeps.jsDepFilenames.map( - (filename) => `${regularBundlePath}/kbn-ui-shared-deps/${filename}` - ), - `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.jsFilename}`, - - `${regularBundlePath}/core/core.entry.js`, - ...kpPluginBundlePaths, - ]; - - // These paths should align with the bundle routes configured in - // src/optimize/bundles_route/bundles_route.ts - const publicPathMap = JSON.stringify({ - core: `${regularBundlePath}/core/`, - 'kbn-ui-shared-deps': `${regularBundlePath}/kbn-ui-shared-deps/`, - ...Object.fromEntries(kpPluginPublicPaths), - }); - - const bootstrap = new AppBootstrap({ - templateData: { - themeTag, - jsDependencyPaths, - styleSheetPaths, - publicPathMap, - }, - }); - - const body = await bootstrap.getJsFile(); - const etag = await bootstrap.getJsFileHash(); - - return h - .response(body) - .header('cache-control', 'must-revalidate') - .header('content-type', 'application/javascript') - .etag(etag); - }, - }); + + kpPluginPublicPaths.set(id, `${regularBundlePath}/plugin/${id}/`); + kpPluginBundlePaths.add(`${regularBundlePath}/plugin/${id}/${id}.plugin.js`); + readKpPlugins(kpUiPlugins.internal.get(id).requiredBundles); + } + })(kpUiPlugins.public.keys()); + + const jsDependencyPaths = [ + ...UiSharedDeps.jsDepFilenames.map( + (filename) => `${regularBundlePath}/kbn-ui-shared-deps/${filename}` + ), + `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.jsFilename}`, + + `${regularBundlePath}/core/core.entry.js`, + ...kpPluginBundlePaths, + ]; + + // These paths should align with the bundle routes configured in + // src/optimize/bundles_route/bundles_route.ts + const publicPathMap = JSON.stringify({ + core: `${regularBundlePath}/core/`, + 'kbn-ui-shared-deps': `${regularBundlePath}/kbn-ui-shared-deps/`, + ...Object.fromEntries(kpPluginPublicPaths), + }); + + const bootstrap = new AppBootstrap({ + templateData: { + themeTag, + jsDependencyPaths, + styleSheetPaths, + publicPathMap, + }, + }); + + const body = await bootstrap.getJsFile(); + const etag = await bootstrap.getJsFileHash(); + + return h + .response(body) + .header('cache-control', 'must-revalidate') + .header('content-type', 'application/javascript') + .etag(etag); + }, }); server.route({ @@ -191,19 +184,17 @@ export function uiRenderMixin(kbnServer, server, config) { }); async function renderApp(h) { - const app = { getId: () => 'core' }; const { http } = kbnServer.newPlatform.setup.core; const { savedObjects } = kbnServer.newPlatform.start.core; - const { rendering, legacy } = kbnServer.newPlatform.__internals; + const { rendering } = kbnServer.newPlatform.__internals; const req = KibanaRequest.from(h.request); const uiSettings = kbnServer.newPlatform.start.core.uiSettings.asScopedToClient( savedObjects.getScopedClient(req) ); - const vars = await legacy.getVars(app.getId(), h.request, { + const vars = { apmConfig: getApmConfig(h.request.path), - }); + }; const content = await rendering.render(h.request, uiSettings, { - app, includeUserSettings: true, vars, }); diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx index fc88b31711b23..abef8afcc3985 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx +++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx @@ -182,6 +182,9 @@ function EditorUI({ initialTextValue }: EditorProps) { unsubscribeResizer(); clearSubscriptions(); window.removeEventListener('hashchange', onHashChange); + if (editorInstanceRef.current) { + editorInstanceRef.current.getCoreEditor().destroy(); + } }; }, [saveCurrentTextObject, initialTextValue, history, setInputEditor, settingsService]); diff --git a/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.ts b/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.ts index 469ef6d79fae5..393b7eee346f5 100644 --- a/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.ts +++ b/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.ts @@ -408,4 +408,8 @@ export class LegacyCoreEditor implements CoreEditor { }, ]); } + + destroy() { + this.editor.destroy(); + } } diff --git a/src/plugins/console/public/types/core_editor.ts b/src/plugins/console/public/types/core_editor.ts index b71f4fff44ca5..d88d8f86b874c 100644 --- a/src/plugins/console/public/types/core_editor.ts +++ b/src/plugins/console/public/types/core_editor.ts @@ -268,4 +268,9 @@ export interface CoreEditor { * detects a change */ registerAutocompleter(autocompleter: AutoCompleterFunction): void; + + /** + * Release any resources in use by the editor. + */ + destroy(): void; } diff --git a/src/plugins/dashboard/public/application/actions/library_notification_action.tsx b/src/plugins/dashboard/public/application/actions/library_notification_action.tsx index 974b55275ccc1..bff0236c802f1 100644 --- a/src/plugins/dashboard/public/application/actions/library_notification_action.tsx +++ b/src/plugins/dashboard/public/application/actions/library_notification_action.tsx @@ -79,6 +79,7 @@ export class LibraryNotificationAction implements ActionByType { return ( + embeddable.getRoot().isContainer && embeddable.getInput()?.viewMode !== ViewMode.VIEW && isReferenceOrValueEmbeddable(embeddable) && embeddable.inputIsRefType(embeddable.getInput()) diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index dd5eb1ee5ccaa..e5b467a418177 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -66,7 +66,6 @@ import { ViewMode, ContainerOutput, EmbeddableInput, - SavedObjectEmbeddableInput, } from '../../../embeddable/public'; import { NavAction, SavedDashboardPanel } from '../types'; @@ -178,7 +177,7 @@ export class DashboardAppController { chrome.docTitle.change(dash.title); } - const incomingEmbeddable = embeddable + let incomingEmbeddable = embeddable .getStateTransfer(scopedHistory()) .getIncomingEmbeddablePackage(); @@ -344,6 +343,22 @@ export class DashboardAppController { dashboardStateManager.getPanels().forEach((panel: SavedDashboardPanel) => { embeddablesMap[panel.panelIndex] = convertSavedDashboardPanelToPanelState(panel); }); + + // If the incoming embeddable state's id already exists in the embeddables map, replace the input, retaining the existing gridData for that panel. + if (incomingEmbeddable?.embeddableId && embeddablesMap[incomingEmbeddable.embeddableId]) { + const originalPanelState = embeddablesMap[incomingEmbeddable.embeddableId]; + embeddablesMap[incomingEmbeddable.embeddableId] = { + gridData: originalPanelState.gridData, + type: incomingEmbeddable.type, + explicitInput: { + ...originalPanelState.explicitInput, + ...incomingEmbeddable.input, + id: incomingEmbeddable.embeddableId, + }, + }; + incomingEmbeddable = undefined; + } + let expandedPanelId; if (dashboardContainer && !isErrorEmbeddable(dashboardContainer)) { expandedPanelId = dashboardContainer.getInput().expandedPanelId; @@ -482,32 +497,16 @@ export class DashboardAppController { refreshDashboardContainer(); }); - if (incomingEmbeddable) { - if ('id' in incomingEmbeddable) { - container.addOrUpdateEmbeddable( - incomingEmbeddable.type, - { - savedObjectId: incomingEmbeddable.id, - } - ); - } else if ('input' in incomingEmbeddable) { - const input = incomingEmbeddable.input; - // @ts-expect-error - delete input.id; - const explicitInput = { - savedVis: input, - }; - const embeddableId = - 'embeddableId' in incomingEmbeddable - ? incomingEmbeddable.embeddableId - : undefined; - container.addOrUpdateEmbeddable( - incomingEmbeddable.type, - // This ugly solution is temporary - https://github.com/elastic/kibana/pull/70272 fixes this whole section - (explicitInput as unknown) as EmbeddableInput, - embeddableId - ); - } + // If the incomingEmbeddable does not yet exist in the panels listing, create a new panel using the container's addEmbeddable method. + if ( + incomingEmbeddable && + (!incomingEmbeddable.embeddableId || + !container.getInput().panels[incomingEmbeddable.embeddableId]) + ) { + container.addNewEmbeddable( + incomingEmbeddable.type, + incomingEmbeddable.input + ); } } diff --git a/src/plugins/dashboard/public/attribute_service/attribute_service_mock.tsx b/src/plugins/dashboard/public/attribute_service/attribute_service.mock.tsx similarity index 100% rename from src/plugins/dashboard/public/attribute_service/attribute_service_mock.tsx rename to src/plugins/dashboard/public/attribute_service/attribute_service.mock.tsx diff --git a/src/plugins/dashboard/public/attribute_service/attribute_service.test.ts b/src/plugins/dashboard/public/attribute_service/attribute_service.test.ts index 06f380ca3862b..ae8f034aec687 100644 --- a/src/plugins/dashboard/public/attribute_service/attribute_service.test.ts +++ b/src/plugins/dashboard/public/attribute_service/attribute_service.test.ts @@ -18,7 +18,7 @@ */ import { ATTRIBUTE_SERVICE_KEY } from './attribute_service'; -import { mockAttributeService } from './attribute_service_mock'; +import { mockAttributeService } from './attribute_service.mock'; import { coreMock } from '../../../../core/public/mocks'; interface TestAttributes { diff --git a/src/plugins/dashboard/public/attribute_service/attribute_service.tsx b/src/plugins/dashboard/public/attribute_service/attribute_service.tsx index 84df05154fb63..7499a6fced72a 100644 --- a/src/plugins/dashboard/public/attribute_service/attribute_service.tsx +++ b/src/plugins/dashboard/public/attribute_service/attribute_service.tsx @@ -156,12 +156,8 @@ export class AttributeService< }; public getExplicitInputFromEmbeddable(embeddable: IEmbeddable): ValType | RefType { - return embeddable.getRoot() && - (embeddable.getRoot() as Container).getInput().panels[embeddable.id].explicitInput - ? ((embeddable.getRoot() as Container).getInput().panels[embeddable.id].explicitInput as - | ValType - | RefType) - : (embeddable.getInput() as ValType | RefType); + return ((embeddable.getRoot() as Container).getInput()?.panels?.[embeddable.id] + ?.explicitInput ?? embeddable.getInput()) as ValType | RefType; } getInputAsValueType = async (input: ValType | RefType): Promise => { @@ -204,7 +200,14 @@ export class AttributeService< const newAttributes = { ...input[ATTRIBUTE_SERVICE_KEY] }; newAttributes.title = props.newTitle; const wrappedInput = (await this.wrapAttributes(newAttributes, true)) as RefType; - resolve(wrappedInput); + + // Remove unneeded attributes from the original input. + delete (input as { [ATTRIBUTE_SERVICE_KEY]?: SavedObjectAttributes })[ + ATTRIBUTE_SERVICE_KEY + ]; + + // Combine input and wrapped input to preserve any passed in explicit Input. + resolve({ ...input, ...wrappedInput }); return { id: wrappedInput.savedObjectId }; } catch (error) { reject(error); diff --git a/src/legacy/plugin_discovery/plugin_exports/index.js b/src/plugins/dashboard/public/attribute_service/index.ts similarity index 91% rename from src/legacy/plugin_discovery/plugin_exports/index.js rename to src/plugins/dashboard/public/attribute_service/index.ts index 0e3511ea85dd4..84d4c8a13c31e 100644 --- a/src/legacy/plugin_discovery/plugin_exports/index.js +++ b/src/plugins/dashboard/public/attribute_service/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { reduceExportSpecs } from './reduce_export_specs'; +export { AttributeService, ATTRIBUTE_SERVICE_KEY } from './attribute_service'; diff --git a/src/plugins/dashboard/public/index.ts b/src/plugins/dashboard/public/index.ts index e22d1f038a456..315afd61c7c44 100644 --- a/src/plugins/dashboard/public/index.ts +++ b/src/plugins/dashboard/public/index.ts @@ -31,7 +31,7 @@ export { } from './application'; export { DashboardConstants, createDashboardEditUrl } from './dashboard_constants'; -export { DashboardStart, DashboardUrlGenerator } from './plugin'; +export { DashboardStart, DashboardUrlGenerator, DashboardFeatureFlagConfig } from './plugin'; export { DASHBOARD_APP_URL_GENERATOR, createDashboardUrlGenerator, @@ -40,7 +40,7 @@ export { export { addEmbeddableToDashboardUrl } from './url_utils/url_helper'; export { SavedObjectDashboard } from './saved_dashboards'; export { SavedDashboardPanel } from './types'; -export { AttributeService, ATTRIBUTE_SERVICE_KEY } from './attribute_service/attribute_service'; +export { AttributeService, ATTRIBUTE_SERVICE_KEY } from './attribute_service'; export function plugin(initializerContext: PluginInitializerContext) { return new DashboardPlugin(initializerContext); diff --git a/src/plugins/dashboard/public/mocks.tsx b/src/plugins/dashboard/public/mocks.tsx index ba30d72594f2a..07f29eca53042 100644 --- a/src/plugins/dashboard/public/mocks.tsx +++ b/src/plugins/dashboard/public/mocks.tsx @@ -20,6 +20,7 @@ import { DashboardStart } from './plugin'; export type Start = jest.Mocked; +export { mockAttributeService } from './attribute_service/attribute_service.mock'; const createStartContract = (): DashboardStart => { // @ts-ignore diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 5a45229a58a7d..eadb3cd207e4d 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -117,7 +117,7 @@ declare module '../../share/public' { export type DashboardUrlGenerator = UrlGeneratorContract; -interface DashboardFeatureFlagConfig { +export interface DashboardFeatureFlagConfig { allowByValueEmbeddables: boolean; } diff --git a/src/plugins/data/common/index.ts b/src/plugins/data/common/index.ts index bc7080e7d450b..153b6a633b66d 100644 --- a/src/plugins/data/common/index.ts +++ b/src/plugins/data/common/index.ts @@ -27,3 +27,10 @@ export * from './query'; export * from './search'; export * from './types'; export * from './utils'; + +/** + * Use data plugin interface instead + * @deprecated + */ + +export { IndexPatternAttributes } from './types'; diff --git a/src/plugins/data/common/index_patterns/errors.ts b/src/plugins/data/common/index_patterns/errors.ts deleted file mode 100644 index 3d92bae1968fb..0000000000000 --- a/src/plugins/data/common/index_patterns/errors.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { FieldSpec } from './types'; - -export class FieldTypeUnknownError extends Error { - public readonly fieldSpec: FieldSpec; - constructor(message: string, spec: FieldSpec) { - super(message); - this.name = 'FieldTypeUnknownError'; - this.fieldSpec = spec; - } -} diff --git a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/bar/index.js b/src/plugins/data/common/index_patterns/errors/duplicate_index_pattern.ts similarity index 83% rename from src/legacy/plugin_discovery/__tests__/fixtures/plugins/bar/index.js rename to src/plugins/data/common/index_patterns/errors/duplicate_index_pattern.ts index 0eef126f2255a..c42dcc1c6a24d 100644 --- a/src/legacy/plugin_discovery/__tests__/fixtures/plugins/bar/index.js +++ b/src/plugins/data/common/index_patterns/errors/duplicate_index_pattern.ts @@ -17,13 +17,9 @@ * under the License. */ -export default function (kibana) { - return [ - new kibana.Plugin({ - id: 'bar:one', - }), - new kibana.Plugin({ - id: 'bar:two', - }), - ]; +export class DuplicateIndexPatternError extends Error { + constructor(message: string) { + super(message); + this.name = 'DuplicateIndexPatternError'; + } } diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_object/index.js b/src/plugins/data/common/index_patterns/errors/index.ts similarity index 94% rename from src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_object/index.js rename to src/plugins/data/common/index_patterns/errors/index.ts index 59f4a2649f019..7cc39d93a2a18 100644 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_object/index.js +++ b/src/plugins/data/common/index_patterns/errors/index.ts @@ -17,6 +17,4 @@ * under the License. */ -export default { - foo: 'bar', -}; +export * from './duplicate_index_pattern'; 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 4cf6075869851..c0eb55a15fead 100644 --- a/src/plugins/data/common/index_patterns/fields/field_list.ts +++ b/src/plugins/data/common/index_patterns/fields/field_list.ts @@ -20,7 +20,7 @@ import { findIndex } from 'lodash'; import { IFieldType } from './types'; import { IndexPatternField } from './index_pattern_field'; -import { OnNotification, FieldSpec } from '../types'; +import { FieldSpec, IndexPatternFieldMap } from '../types'; import { IndexPattern } from '../index_patterns'; import { shortenDottedString } from '../../utils'; @@ -35,16 +35,11 @@ export interface IIndexPatternFieldList extends Array { removeAll(): void; replaceAll(specs: FieldSpec[]): void; update(field: FieldSpec): void; - toSpec(options?: { getFormatterForField?: IndexPattern['getFormatterForField'] }): FieldSpec[]; + toSpec(options?: { + getFormatterForField?: IndexPattern['getFormatterForField']; + }): IndexPatternFieldMap; } -export type CreateIndexPatternFieldList = ( - indexPattern: IndexPattern, - specs?: FieldSpec[], - shortDotsEnable?: boolean, - onNotification?: OnNotification -) => IIndexPatternFieldList; - // extending the array class and using a constructor doesn't work well // when calling filter and similar so wrapping in a callback. // to be removed in the future @@ -105,7 +100,7 @@ export const fieldList = ( this.groups.clear(); }; - public readonly replaceAll = (spcs: FieldSpec[]) => { + public readonly replaceAll = (spcs: FieldSpec[] = []) => { this.removeAll(); spcs.forEach(this.add); }; @@ -115,7 +110,12 @@ export const fieldList = ( }: { getFormatterForField?: IndexPattern['getFormatterForField']; } = {}) { - return [...this.map((field) => field.toSpec({ getFormatterForField }))]; + return { + ...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 7f72bfe55c7cd..808afc3449c2a 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 @@ -17,12 +17,9 @@ * under the License. */ -import { i18n } from '@kbn/i18n'; import { KbnFieldType, getKbnFieldType } from '../../kbn_field_types'; -import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { IFieldType } from './types'; import { FieldSpec, IndexPattern } from '../..'; -import { FieldTypeUnknownError } from '../errors'; export class IndexPatternField implements IFieldType { readonly spec: FieldSpec; @@ -35,16 +32,12 @@ export class IndexPatternField implements IFieldType { this.displayName = displayName; this.kbnFieldType = getKbnFieldType(spec.type); - if (spec.type && this.kbnFieldType?.name === KBN_FIELD_TYPES.UNKNOWN) { - const msg = i18n.translate('data.indexPatterns.unknownFieldTypeErrorMsg', { - values: { type: spec.type, name: spec.name }, - defaultMessage: `Field '{name}' Unknown field type '{type}'`, - }); - throw new FieldTypeUnknownError(msg, spec); - } } // writable attrs + /** + * Count is used for field popularity + */ public get count() { return this.spec.count || 0; } @@ -53,6 +46,9 @@ export class IndexPatternField implements IFieldType { this.spec.count = count; } + /** + * Script field code + */ public get script() { return this.spec.script; } @@ -61,6 +57,9 @@ export class IndexPatternField implements IFieldType { this.spec.script = script; } + /** + * Script field language + */ public get lang() { return this.spec.lang; } @@ -69,6 +68,9 @@ export class IndexPatternField implements IFieldType { this.spec.lang = lang; } + /** + * Description of field type conflicts across different indices in the same index pattern + */ public get conflictDescriptions() { return this.spec.conflictDescriptions; } @@ -152,7 +154,7 @@ export class IndexPatternField implements IFieldType { getFormatterForField, }: { getFormatterForField?: IndexPattern['getFormatterForField']; - } = {}) { + } = {}): FieldSpec { return { count: this.count, script: this.script, diff --git a/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap index 1871627da76de..ed84aceb60e5a 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap +++ b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap @@ -2,13 +2,13 @@ exports[`IndexPattern toSpec should match snapshot 1`] = ` Object { - "fields": Array [ - Object { + "fields": Object { + "@tags": Object { "aggregatable": true, "conflictDescriptions": undefined, - "count": 10, + "count": 0, "esTypes": Array [ - "long", + "keyword", ], "format": Object { "id": "number", @@ -17,20 +17,20 @@ Object { }, }, "lang": undefined, - "name": "bytes", + "name": "@tags", "readFromDocValues": true, "script": undefined, "scripted": false, "searchable": true, "subType": undefined, - "type": "number", + "type": "string", }, - Object { + "@timestamp": Object { "aggregatable": true, "conflictDescriptions": undefined, - "count": 20, + "count": 30, "esTypes": Array [ - "boolean", + "date", ], "format": Object { "id": "number", @@ -39,20 +39,20 @@ Object { }, }, "lang": undefined, - "name": "ssl", + "name": "@timestamp", "readFromDocValues": true, "script": undefined, "scripted": false, "searchable": true, "subType": undefined, - "type": "boolean", + "type": "date", }, - Object { + "_id": Object { "aggregatable": true, "conflictDescriptions": undefined, - "count": 30, + "count": 0, "esTypes": Array [ - "date", + "_id", ], "format": Object { "id": "number", @@ -61,20 +61,20 @@ Object { }, }, "lang": undefined, - "name": "@timestamp", - "readFromDocValues": true, + "name": "_id", + "readFromDocValues": false, "script": undefined, "scripted": false, "searchable": true, "subType": undefined, - "type": "date", + "type": "string", }, - Object { + "_source": Object { "aggregatable": true, "conflictDescriptions": undefined, - "count": 30, + "count": 0, "esTypes": Array [ - "date", + "_source", ], "format": Object { "id": "number", @@ -83,20 +83,20 @@ Object { }, }, "lang": undefined, - "name": "time", - "readFromDocValues": true, + "name": "_source", + "readFromDocValues": false, "script": undefined, "scripted": false, "searchable": true, "subType": undefined, - "type": "date", + "type": "_source", }, - Object { + "_type": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "keyword", + "_type", ], "format": Object { "id": "number", @@ -105,20 +105,20 @@ Object { }, }, "lang": undefined, - "name": "@tags", - "readFromDocValues": true, + "name": "_type", + "readFromDocValues": false, "script": undefined, "scripted": false, "searchable": true, "subType": undefined, "type": "string", }, - Object { + "area": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "date", + "geo_shape", ], "format": Object { "id": "number", @@ -127,20 +127,20 @@ Object { }, }, "lang": undefined, - "name": "utc_time", - "readFromDocValues": true, + "name": "area", + "readFromDocValues": false, "script": undefined, "scripted": false, "searchable": true, "subType": undefined, - "type": "date", + "type": "geo_shape", }, - Object { + "bytes": Object { "aggregatable": true, "conflictDescriptions": undefined, - "count": 0, + "count": 10, "esTypes": Array [ - "integer", + "long", ], "format": Object { "id": "number", @@ -149,7 +149,7 @@ Object { }, }, "lang": undefined, - "name": "phpmemory", + "name": "bytes", "readFromDocValues": true, "script": undefined, "scripted": false, @@ -157,12 +157,12 @@ Object { "subType": undefined, "type": "number", }, - Object { + "custom_user_field": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "ip", + "conflict", ], "format": Object { "id": "number", @@ -171,20 +171,20 @@ Object { }, }, "lang": undefined, - "name": "ip", + "name": "custom_user_field", "readFromDocValues": true, "script": undefined, "scripted": false, "searchable": true, "subType": undefined, - "type": "ip", + "type": "conflict", }, - Object { + "extension": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "attachment", + "text", ], "format": Object { "id": "number", @@ -193,15 +193,41 @@ Object { }, }, "lang": undefined, - "name": "request_body", - "readFromDocValues": true, + "name": "extension", + "readFromDocValues": false, "script": undefined, "scripted": false, "searchable": true, "subType": undefined, - "type": "attachment", + "type": "string", }, - Object { + "extension.keyword": Object { + "aggregatable": true, + "conflictDescriptions": undefined, + "count": 0, + "esTypes": Array [ + "keyword", + ], + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, + "lang": undefined, + "name": "extension.keyword", + "readFromDocValues": true, + "script": undefined, + "scripted": false, + "searchable": true, + "subType": Object { + "multi": Object { + "parent": "extension", + }, + }, + "type": "string", + }, + "geo.coordinates": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, @@ -215,7 +241,7 @@ Object { }, }, "lang": undefined, - "name": "point", + "name": "geo.coordinates", "readFromDocValues": true, "script": undefined, "scripted": false, @@ -223,12 +249,12 @@ Object { "subType": undefined, "type": "geo_point", }, - Object { + "geo.src": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "geo_shape", + "keyword", ], "format": Object { "id": "number", @@ -237,15 +263,15 @@ Object { }, }, "lang": undefined, - "name": "area", - "readFromDocValues": false, + "name": "geo.src", + "readFromDocValues": true, "script": undefined, "scripted": false, "searchable": true, "subType": undefined, - "type": "geo_shape", + "type": "string", }, - Object { + "hashed": Object { "aggregatable": false, "conflictDescriptions": undefined, "count": 0, @@ -267,12 +293,12 @@ Object { "subType": undefined, "type": "murmur3", }, - Object { + "ip": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "geo_point", + "ip", ], "format": Object { "id": "number", @@ -281,15 +307,15 @@ Object { }, }, "lang": undefined, - "name": "geo.coordinates", + "name": "ip", "readFromDocValues": true, "script": undefined, "scripted": false, "searchable": true, "subType": undefined, - "type": "geo_point", + "type": "ip", }, - Object { + "machine.os": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, @@ -303,7 +329,7 @@ Object { }, }, "lang": undefined, - "name": "extension", + "name": "machine.os", "readFromDocValues": false, "script": undefined, "scripted": false, @@ -311,7 +337,7 @@ Object { "subType": undefined, "type": "string", }, - Object { + "machine.os.raw": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, @@ -325,19 +351,19 @@ Object { }, }, "lang": undefined, - "name": "extension.keyword", + "name": "machine.os.raw", "readFromDocValues": true, "script": undefined, "scripted": false, "searchable": true, "subType": Object { "multi": Object { - "parent": "extension", + "parent": "machine.os", }, }, "type": "string", }, - Object { + "non-filterable": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, @@ -351,20 +377,20 @@ Object { }, }, "lang": undefined, - "name": "machine.os", + "name": "non-filterable", "readFromDocValues": false, "script": undefined, "scripted": false, - "searchable": true, + "searchable": false, "subType": undefined, "type": "string", }, - Object { - "aggregatable": true, + "non-sortable": Object { + "aggregatable": false, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "keyword", + "text", ], "format": Object { "id": "number", @@ -373,24 +399,20 @@ Object { }, }, "lang": undefined, - "name": "machine.os.raw", - "readFromDocValues": true, + "name": "non-sortable", + "readFromDocValues": false, "script": undefined, "scripted": false, - "searchable": true, - "subType": Object { - "multi": Object { - "parent": "machine.os", - }, - }, + "searchable": false, + "subType": undefined, "type": "string", }, - Object { + "phpmemory": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "keyword", + "integer", ], "format": Object { "id": "number", @@ -399,20 +421,20 @@ Object { }, }, "lang": undefined, - "name": "geo.src", + "name": "phpmemory", "readFromDocValues": true, "script": undefined, "scripted": false, "searchable": true, "subType": undefined, - "type": "string", + "type": "number", }, - Object { + "point": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "_id", + "geo_point", ], "format": Object { "id": "number", @@ -421,20 +443,20 @@ Object { }, }, "lang": undefined, - "name": "_id", - "readFromDocValues": false, + "name": "point", + "readFromDocValues": true, "script": undefined, "scripted": false, "searchable": true, "subType": undefined, - "type": "string", + "type": "geo_point", }, - Object { + "request_body": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "_type", + "attachment", ], "format": Object { "id": "number", @@ -443,20 +465,20 @@ Object { }, }, "lang": undefined, - "name": "_type", - "readFromDocValues": false, + "name": "request_body", + "readFromDocValues": true, "script": undefined, "scripted": false, "searchable": true, "subType": undefined, - "type": "string", + "type": "attachment", }, - Object { + "script date": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "_source", + "date", ], "format": Object { "id": "number", @@ -464,43 +486,21 @@ Object { "pattern": "$0,0.[00]", }, }, - "lang": undefined, - "name": "_source", + "lang": "painless", + "name": "script date", "readFromDocValues": false, - "script": undefined, - "scripted": false, + "script": "1234", + "scripted": true, "searchable": true, "subType": undefined, - "type": "_source", + "type": "date", }, - Object { + "script murmur3": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "text", - ], - "format": Object { - "id": "number", - "params": Object { - "pattern": "$0,0.[00]", - }, - }, - "lang": undefined, - "name": "non-filterable", - "readFromDocValues": false, - "script": undefined, - "scripted": false, - "searchable": false, - "subType": undefined, - "type": "string", - }, - Object { - "aggregatable": false, - "conflictDescriptions": undefined, - "count": 0, - "esTypes": Array [ - "text", + "murmur3", ], "format": Object { "id": "number", @@ -508,21 +508,21 @@ Object { "pattern": "$0,0.[00]", }, }, - "lang": undefined, - "name": "non-sortable", + "lang": "expression", + "name": "script murmur3", "readFromDocValues": false, - "script": undefined, - "scripted": false, - "searchable": false, + "script": "1234", + "scripted": true, + "searchable": true, "subType": undefined, - "type": "string", + "type": "murmur3", }, - Object { + "script number": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "conflict", + "long", ], "format": Object { "id": "number", @@ -530,16 +530,16 @@ Object { "pattern": "$0,0.[00]", }, }, - "lang": undefined, - "name": "custom_user_field", - "readFromDocValues": true, - "script": undefined, - "scripted": false, + "lang": "expression", + "name": "script number", + "readFromDocValues": false, + "script": "1234", + "scripted": true, "searchable": true, "subType": undefined, - "type": "conflict", + "type": "number", }, - Object { + "script string": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, @@ -561,12 +561,12 @@ Object { "subType": undefined, "type": "string", }, - Object { + "ssl": Object { "aggregatable": true, "conflictDescriptions": undefined, - "count": 0, + "count": 20, "esTypes": Array [ - "long", + "boolean", ], "format": Object { "id": "number", @@ -574,19 +574,19 @@ Object { "pattern": "$0,0.[00]", }, }, - "lang": "expression", - "name": "script number", - "readFromDocValues": false, - "script": "1234", - "scripted": true, + "lang": undefined, + "name": "ssl", + "readFromDocValues": true, + "script": undefined, + "scripted": false, "searchable": true, "subType": undefined, - "type": "number", + "type": "boolean", }, - Object { + "time": Object { "aggregatable": true, "conflictDescriptions": undefined, - "count": 0, + "count": 30, "esTypes": Array [ "date", ], @@ -596,21 +596,21 @@ Object { "pattern": "$0,0.[00]", }, }, - "lang": "painless", - "name": "script date", - "readFromDocValues": false, - "script": "1234", - "scripted": true, + "lang": undefined, + "name": "time", + "readFromDocValues": true, + "script": undefined, + "scripted": false, "searchable": true, "subType": undefined, "type": "date", }, - Object { + "utc_time": Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, "esTypes": Array [ - "murmur3", + "date", ], "format": Object { "id": "number", @@ -618,21 +618,22 @@ Object { "pattern": "$0,0.[00]", }, }, - "lang": "expression", - "name": "script murmur3", - "readFromDocValues": false, - "script": "1234", - "scripted": true, + "lang": undefined, + "name": "utc_time", + "readFromDocValues": true, + "script": undefined, + "scripted": false, "searchable": true, "subType": undefined, - "type": "murmur3", + "type": "date", }, - ], + }, "id": "test-pattern", "sourceFilters": undefined, "timeFieldName": "timestamp", "title": "title", + "type": "index-pattern", "typeMeta": undefined, - "version": 2, + "version": "2", } `; diff --git a/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_patterns.test.ts.snap b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_patterns.test.ts.snap new file mode 100644 index 0000000000000..752fdcf11991c --- /dev/null +++ b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_patterns.test.ts.snap @@ -0,0 +1,22 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`IndexPatterns savedObjectToSpec 1`] = ` +Object { + "fields": Object {}, + "id": "id", + "intervalName": undefined, + "sourceFilters": Array [ + Object { + "value": "item1", + }, + Object { + "value": "item2", + }, + ], + "timeFieldName": "@timestamp", + "title": "kibana-*", + "type": "", + "typeMeta": Object {}, + "version": "version", +} +`; diff --git a/src/plugins/data/common/index_patterns/index_patterns/_fields_fetcher.ts b/src/plugins/data/common/index_patterns/index_patterns/_fields_fetcher.ts deleted file mode 100644 index 4eba0576ff235..0000000000000 --- a/src/plugins/data/common/index_patterns/index_patterns/_fields_fetcher.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { IndexPattern } from '.'; -import { GetFieldsOptions, IIndexPatternsApiClient } from '../types'; - -/** @internal */ -export const createFieldsFetcher = ( - indexPattern: IndexPattern, - apiClient: IIndexPatternsApiClient, - metaFields: string[] = [] -) => { - const fieldFetcher = { - fetch: (options: GetFieldsOptions) => { - return fieldFetcher.fetchForWildcard(indexPattern.title, { - ...options, - type: indexPattern.type, - params: indexPattern.typeMeta && indexPattern.typeMeta.params, - }); - }, - fetchForWildcard: (pattern: string, options: GetFieldsOptions = {}) => { - return apiClient.getFieldsForWildcard({ - pattern, - metaFields, - type: options.type, - params: options.params || {}, - }); - }, - }; - - return fieldFetcher; -}; diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts index f49897c47d562..91286a38f16a0 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts @@ -17,19 +17,18 @@ * under the License. */ -import { defaults, map, last } from 'lodash'; +import { map, last } from 'lodash'; import { IndexPattern } from './index_pattern'; import { DuplicateField } from '../../../../kibana_utils/common'; -// @ts-ignore +// @ts-expect-error import mockLogStashFields from '../../../../../fixtures/logstash_fields'; -// @ts-ignore import { stubbedSavedObjectIndexPattern } from '../../../../../fixtures/stubbed_saved_object_index_pattern'; import { IndexPatternField } from '../fields'; import { fieldFormatsMock } from '../../field_formats/mocks'; -import { FieldFormat, IndexPatternsService } from '../..'; +import { FieldFormat } from '../..'; class MockFieldFormatter {} @@ -63,90 +62,33 @@ jest.mock('../../field_mapping', () => { }; }); -let mockFieldsFetcherResponse: any[] = []; - -jest.mock('./_fields_fetcher', () => ({ - createFieldsFetcher: jest.fn().mockImplementation(() => ({ - fetch: jest.fn().mockImplementation(() => { - return new Promise((resolve) => resolve(mockFieldsFetcherResponse)); - }), - every: jest.fn(), - })), -})); - -let object: any = {}; - -const savedObjectsClient = { - create: jest.fn(), - get: jest.fn().mockImplementation(() => object), - update: jest.fn().mockImplementation(async (type, id, body, { version }) => { - if (object.version !== version) { - throw new Object({ - res: { - status: 409, - }, - }); - } - object.attributes.title = body.title; - object.version += 'a'; - return { - id: object.id, - version: object.version, - }; - }), -}; - -const patternCache = { - clear: jest.fn(), - get: jest.fn(), - set: jest.fn(), - clearAll: jest.fn(), -}; - -const apiClient = { - _getUrl: jest.fn(), - getFieldsForTimePattern: jest.fn(), - getFieldsForWildcard: jest.fn(), -}; - // helper function to create index patterns -function create(id: string, payload?: any): Promise { - const indexPattern = new IndexPattern(id, { - savedObjectsClient: savedObjectsClient as any, - apiClient, - patternCache, +function create(id: string) { + const { + type, + version, + attributes: { timeFieldName, fields, title }, + } = stubbedSavedObjectIndexPattern(id); + + return new IndexPattern({ + spec: { id, type, version, timeFieldName, fields, title }, + savedObjectsClient: {} as any, fieldFormats: fieldFormatsMock, - indexPatternsService: {} as IndexPatternsService, - onNotification: () => {}, - onError: () => {}, shortDotsEnable: false, metaFields: [], }); - - setDocsourcePayload(id, payload); - - return indexPattern.init(); -} - -function setDocsourcePayload(id: string | null, providedPayload: any) { - object = defaults(providedPayload || {}, stubbedSavedObjectIndexPattern(id)); } describe('IndexPattern', () => { - const indexPatternId = 'test-pattern'; - let indexPattern: IndexPattern; // create an indexPattern instance for each test beforeEach(() => { - return create(indexPatternId).then((pattern: IndexPattern) => { - indexPattern = pattern; - }); + indexPattern = create('test-pattern'); }); describe('api', () => { test('should have expected properties', () => { - expect(indexPattern).toHaveProperty('refreshFields'); expect(indexPattern).toHaveProperty('popularizeField'); expect(indexPattern).toHaveProperty('getScriptedFields'); expect(indexPattern).toHaveProperty('getNonScriptedFields'); @@ -158,13 +100,6 @@ describe('IndexPattern', () => { }); }); - describe('init', () => { - test('should append the found fields', () => { - expect(savedObjectsClient.get).toHaveBeenCalled(); - expect(indexPattern.fields).toHaveLength(mockLogStashFields().length); - }); - }); - describe('fields', () => { test('should have expected properties on fields', function () { expect(indexPattern.fields[0]).toHaveProperty('displayName'); @@ -229,43 +164,9 @@ describe('IndexPattern', () => { }); }); - describe('refresh fields', () => { - test('should fetch fields from the fieldsFetcher', async () => { - expect(indexPattern.fields.length).toBeGreaterThan(2); - - mockFieldsFetcherResponse = [{ name: 'foo' }, { name: 'bar' }]; - - await indexPattern.refreshFields(); - - mockFieldsFetcherResponse = []; - - const newFields = indexPattern.getNonScriptedFields(); - - expect(newFields).toHaveLength(2); - expect([...newFields.map((f) => f.name)]).toEqual(['foo', 'bar']); - }); - - test('should preserve the scripted fields', async () => { - // add spy to indexPattern.getScriptedFields - // sinon.spy(indexPattern, 'getScriptedFields'); - - // refresh fields, which will fetch - await indexPattern.refreshFields(); - - // called to append scripted fields to the response from mapper.getFieldsForIndexPattern - // sinon.assert.calledOnce(indexPattern.getScriptedFields); - expect(indexPattern.getScriptedFields().map((f) => f.name)).toEqual( - mockLogStashFields() - .filter((f: IndexPatternField) => f.scripted) - .map((f: IndexPatternField) => f.name) - ); - }); - }); - describe('add and remove scripted fields', () => { test('should append the scripted field', async () => { // keep a copy of the current scripted field count - // const saveSpy = sinon.spy(indexPattern, 'save'); const oldCount = indexPattern.getScriptedFields().length; // add a new scripted field @@ -283,7 +184,6 @@ describe('IndexPattern', () => { ); const scriptedFields = indexPattern.getScriptedFields(); - // expect(saveSpy.callCount).to.equal(1); expect(scriptedFields).toHaveLength(oldCount + 1); expect((indexPattern.fields.getByName(scriptedField.name) as IndexPatternField).name).toEqual( scriptedField.name @@ -291,14 +191,12 @@ describe('IndexPattern', () => { }); test('should remove scripted field, by name', async () => { - // const saveSpy = sinon.spy(indexPattern, 'save'); const scriptedFields = indexPattern.getScriptedFields(); const oldCount = scriptedFields.length; const scriptedField = last(scriptedFields)!; await indexPattern.removeScriptedField(scriptedField.name); - // expect(saveSpy.callCount).to.equal(1); expect(indexPattern.getScriptedFields().length).toEqual(oldCount - 1); expect(indexPattern.fields.getByName(scriptedField.name)).toEqual(undefined); }); @@ -330,8 +228,13 @@ describe('IndexPattern', () => { } as FieldFormat; indexPattern.getFormatterForField = () => formatter; const spec = indexPattern.toSpec(); - const restoredPattern = await create(spec.id as string); - restoredPattern.initFromSpec(spec); + const restoredPattern = new IndexPattern({ + spec, + savedObjectsClient: {} as any, + fieldFormats: fieldFormatsMock, + shortDotsEnable: false, + metaFields: [], + }); expect(restoredPattern.id).toEqual(indexPattern.id); expect(restoredPattern.title).toEqual(indexPattern.title); expect(restoredPattern.timeFieldName).toEqual(indexPattern.timeFieldName); @@ -342,26 +245,22 @@ describe('IndexPattern', () => { describe('popularizeField', () => { test('should increment the popularity count by default', () => { - // const saveSpy = sinon.stub(indexPattern, 'save'); indexPattern.fields.forEach(async (field) => { const oldCount = field.count || 0; await indexPattern.popularizeField(field.name); - // expect(saveSpy.callCount).to.equal(i + 1); expect(field.count).toEqual(oldCount + 1); }); }); test('should increment the popularity count', () => { - // const saveSpy = sinon.stub(indexPattern, 'save'); indexPattern.fields.forEach(async (field) => { const oldCount = field.count || 0; const incrementAmount = 4; await indexPattern.popularizeField(field.name, incrementAmount); - // expect(saveSpy.callCount).to.equal(i + 1); expect(field.count).toEqual(oldCount + incrementAmount); }); }); 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 76f1a5e59d0ee..882235889b55c 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 @@ -18,211 +18,94 @@ */ import _, { each, reject } from 'lodash'; -import { i18n } from '@kbn/i18n'; import { SavedObjectsClientCommon } from '../..'; -import { DuplicateField, SavedObjectNotFound } from '../../../../kibana_utils/common'; +import { DuplicateField } from '../../../../kibana_utils/common'; import { ES_FIELD_TYPES, KBN_FIELD_TYPES, IIndexPattern, - FieldTypeUnknownError, FieldFormatNotFoundError, + IFieldType, } from '../../../common'; -import { findByTitle } from '../utils'; -import { IndexPatternMissingIndices } from '../lib'; import { IndexPatternField, IIndexPatternFieldList, fieldList } from '../fields'; -import { createFieldsFetcher } from './_fields_fetcher'; import { formatHitProvider } from './format_hit'; import { flattenHitWrapper } from './flatten_hit'; -import { OnNotification, OnError, IIndexPatternsApiClient, IndexPatternAttributes } from '../types'; import { FieldFormatsStartCommon, FieldFormat } from '../../field_formats'; -import { PatternCache } from './_pattern_cache'; -import { expandShorthand, FieldMappingSpec, MappingObject } from '../../field_mapping'; -import { IndexPatternSpec, TypeMeta, FieldSpec, SourceFilter } from '../types'; +import { IndexPatternSpec, TypeMeta, SourceFilter, IndexPatternFieldMap } from '../types'; import { SerializedFieldFormat } from '../../../../expressions/common'; -import { IndexPatternsService } from '..'; - -const savedObjectType = 'index-pattern'; interface IndexPatternDeps { + spec?: IndexPatternSpec; savedObjectsClient: SavedObjectsClientCommon; - apiClient: IIndexPatternsApiClient; - patternCache: PatternCache; fieldFormats: FieldFormatsStartCommon; - indexPatternsService: IndexPatternsService; - onNotification: OnNotification; - onError: OnError; shortDotsEnable: boolean; metaFields: string[]; } +interface SavedObjectBody { + title?: string; + timeFieldName?: string; + intervalName?: string; + fields?: string; + sourceFilters?: string; + fieldFormatMap?: string; + typeMeta?: string; + type?: string; +} + +type FormatFieldFn = (hit: Record, fieldName: string) => any; + export class IndexPattern implements IIndexPattern { public id?: string; public title: string = ''; - public fieldFormatMap: any; + public fieldFormatMap: Record; public typeMeta?: TypeMeta; - public fields: IIndexPatternFieldList & { toSpec: () => FieldSpec[] }; + public fields: IIndexPatternFieldList & { toSpec: () => IndexPatternFieldMap }; public timeFieldName: string | undefined; public intervalName: string | undefined; public type: string | undefined; - public formatHit: any; - public formatField: any; - public flattenHit: any; + public formatHit: { + (hit: Record, type?: string): any; + formatField: FormatFieldFn; + }; + public formatField: FormatFieldFn; + public flattenHit: (hit: Record, deep?: boolean) => Record; public metaFields: string[]; - + // savedObject version public version: string | undefined; private savedObjectsClient: SavedObjectsClientCommon; - private patternCache: PatternCache; public sourceFilters?: SourceFilter[]; - // todo make read only, update via method or factor out - public originalBody: { [key: string]: any } = {}; - public fieldsFetcher: any; // probably want to factor out any direct usage and change to private - private indexPatternsService: IndexPatternsService; + private originalSavedObjectBody: SavedObjectBody = {}; private shortDotsEnable: boolean = false; private fieldFormats: FieldFormatsStartCommon; - private onNotification: OnNotification; - private onError: OnError; - - private mapping: MappingObject = expandShorthand({ - title: ES_FIELD_TYPES.TEXT, - timeFieldName: ES_FIELD_TYPES.KEYWORD, - intervalName: ES_FIELD_TYPES.KEYWORD, - fields: 'json', - sourceFilters: 'json', - fieldFormatMap: { - type: ES_FIELD_TYPES.TEXT, - _serialize: (map = {}) => { - const serialized = _.transform(map, this.serializeFieldFormatMap); - return _.isEmpty(serialized) ? undefined : JSON.stringify(serialized); - }, - _deserialize: (map = '{}') => { - return _.mapValues(JSON.parse(map), (mapping) => { - return this.deserializeFieldFormatMap(mapping); - }); - }, - }, - type: ES_FIELD_TYPES.KEYWORD, - typeMeta: 'json', - }); - - constructor( - id: string | undefined, - { - savedObjectsClient, - apiClient, - patternCache, - fieldFormats, - indexPatternsService, - onNotification, - onError, - shortDotsEnable = false, - metaFields = [], - }: IndexPatternDeps - ) { - this.id = id; + + constructor({ + spec = {}, + savedObjectsClient, + fieldFormats, + shortDotsEnable = false, + metaFields = [], + }: IndexPatternDeps) { + // set dependencies this.savedObjectsClient = savedObjectsClient; - this.patternCache = patternCache; this.fieldFormats = fieldFormats; - this.indexPatternsService = indexPatternsService; - this.onNotification = onNotification; - this.onError = onError; - + // set config this.shortDotsEnable = shortDotsEnable; this.metaFields = metaFields; + // initialize functionality this.fields = fieldList([], this.shortDotsEnable); - this.fieldsFetcher = createFieldsFetcher(this, apiClient, metaFields); this.flattenHit = flattenHitWrapper(this, metaFields); this.formatHit = formatHitProvider( this, fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.STRING) ); this.formatField = this.formatHit.formatField; - } - - private unknownFieldErrorNotification( - fieldType: string, - fieldName: string, - indexPatternTitle: string - ) { - const title = i18n.translate('data.indexPatterns.unknownFieldHeader', { - values: { type: fieldType }, - defaultMessage: 'Unknown field type {type}', - }); - const text = i18n.translate('data.indexPatterns.unknownFieldErrorMessage', { - values: { name: fieldName, title: indexPatternTitle }, - defaultMessage: 'Field {name} in indexPattern {title} is using an unknown field type.', - }); - this.onNotification({ title, text, color: 'danger', iconType: 'alert' }); - } - - private serializeFieldFormatMap(flat: any, format: string, field: string | undefined) { - if (format && field) { - flat[field] = format; - } - } - - private deserializeFieldFormatMap(mapping: any) { - try { - return this.fieldFormats.getInstance(mapping.id, mapping.params); - } catch (err) { - if (err instanceof FieldFormatNotFoundError) { - return undefined; - } else { - throw err; - } - } - } - - private isFieldRefreshRequired(specs?: FieldSpec[]): boolean { - if (!specs) { - return true; - } - - return specs.every((spec) => { - // See https://github.com/elastic/kibana/pull/8421 - const hasFieldCaps = 'aggregatable' in spec && 'searchable' in spec; - - // See https://github.com/elastic/kibana/pull/11969 - const hasDocValuesFlag = 'readFromDocValues' in spec; - - return !hasFieldCaps || !hasDocValuesFlag; - }); - } - - private async indexFields(specs?: FieldSpec[]) { - if (!this.id) { - return; - } - - if (this.isFieldRefreshRequired(specs)) { - await this.refreshFields(); - } else { - if (specs) { - try { - this.fields.replaceAll(specs); - } catch (err) { - if (err instanceof FieldTypeUnknownError) { - this.unknownFieldErrorNotification(err.fieldSpec.name, err.fieldSpec.type, this.title); - } else { - throw err; - } - } - } - } - } - public initFromSpec(spec: IndexPatternSpec) { - // create fieldFormatMap from field list - const fieldFormatMap: Record = {}; - if (_.isArray(spec.fields)) { - spec.fields.forEach((field: FieldSpec) => { - if (field.format) { - fieldFormatMap[field.name as string] = { ...field.format }; - } - }); - } + // set values + this.id = spec.id; + const fieldFormatMap = this.fieldSpecsToFieldFormatMap(spec.fields); this.version = spec.version; @@ -230,53 +113,55 @@ export class IndexPattern implements IIndexPattern { this.timeFieldName = spec.timeFieldName; this.sourceFilters = spec.sourceFilters; - try { - this.fields.replaceAll(spec.fields || []); - } catch (err) { - if (err instanceof FieldTypeUnknownError) { - this.unknownFieldErrorNotification(err.fieldSpec.name, err.fieldSpec.type, this.title); - } else { - throw err; - } - } + this.fields.replaceAll(Object.values(spec.fields || {})); + this.type = spec.type; this.typeMeta = spec.typeMeta; this.fieldFormatMap = _.mapValues(fieldFormatMap, (mapping) => { return this.deserializeFieldFormatMap(mapping); }); - - return this; } - private updateFromElasticSearch(response: any) { - if (!response.found) { - throw new SavedObjectNotFound(savedObjectType, this.id, 'management/kibana/indexPatterns'); - } - - _.forOwn(this.mapping, (fieldMapping: FieldMappingSpec, name: string | undefined) => { - if (!fieldMapping._deserialize || !name) { - return; + /** + * Get last saved saved object fields + */ + getOriginalSavedObjectBody = () => ({ ...this.originalSavedObjectBody }); + + /** + * Reset last saved saved object fields. used after saving + */ + resetOriginalSavedObjectBody = () => { + this.originalSavedObjectBody = this.getAsSavedObjectBody(); + }; + + /** + * Converts field format spec to field format instance + * @param mapping + */ + private deserializeFieldFormatMap(mapping: SerializedFieldFormat>) { + try { + return this.fieldFormats.getInstance(mapping.id as string, mapping.params); + } catch (err) { + if (err instanceof FieldFormatNotFoundError) { + return undefined; + } else { + throw err; } - - response[name] = fieldMapping._deserialize(response[name]); - }); - - this.title = response.title; - this.timeFieldName = response.timeFieldName; - this.intervalName = response.intervalName; - this.sourceFilters = response.sourceFilters; - this.fieldFormatMap = response.fieldFormatMap; - this.type = response.type; - this.typeMeta = response.typeMeta; - - if (!this.title && this.id) { - this.title = this.id; } - this.version = response.version; - - return this.indexFields(response.fields); } + /** + * Extracts FieldFormatMap from FieldSpec map + * @param fldList FieldSpec map + */ + private fieldSpecsToFieldFormatMap = (fldList: IndexPatternSpec['fields'] = {}) => + Object.values(fldList).reduce>((col, fieldSpec) => { + if (fieldSpec.format) { + col[fieldSpec.name] = { ...fieldSpec.format }; + } + return col; + }, {}); + getComputedFields() { const scriptFields: any = {}; if (!this.fields) { @@ -318,37 +203,6 @@ export class IndexPattern implements IIndexPattern { }; } - async init() { - if (!this.id) { - return this; // no id === no elasticsearch document - } - - const savedObject = await this.savedObjectsClient.get( - savedObjectType, - this.id - ); - - const response = { - version: savedObject.version, - found: savedObject.version ? true : false, - title: savedObject.attributes.title, - timeFieldName: savedObject.attributes.timeFieldName, - intervalName: savedObject.attributes.intervalName, - fields: savedObject.attributes.fields, - sourceFilters: savedObject.attributes.sourceFilters, - fieldFormatMap: savedObject.attributes.fieldFormatMap, - typeMeta: savedObject.attributes.typeMeta, - type: savedObject.attributes.type, - }; - // Do this before we attempt to update from ES since that call can potentially perform a save - this.originalBody = this.prepBody(); - await this.updateFromElasticSearch(response); - // Do it after to ensure we have the most up to date information - this.originalBody = this.prepBody(); - - return this; - } - public toSpec(): IndexPatternSpec { return { id: this.id, @@ -359,17 +213,33 @@ export class IndexPattern implements IIndexPattern { sourceFilters: this.sourceFilters, fields: this.fields.toSpec({ getFormatterForField: this.getFormatterForField.bind(this) }), typeMeta: this.typeMeta, + type: this.type, }; } - // Get the source filtering configuration for that index. + /** + * Get the source filtering configuration for that index. + */ getSourceFiltering() { return { excludes: (this.sourceFilters && this.sourceFilters.map((filter: any) => filter.value)) || [], }; } - async addScriptedField(name: string, script: string, fieldType: string = 'string', lang: string) { + /** + * Add scripted field to field list + * + * @param name field name + * @param script script code + * @param fieldType + * @param lang + */ + async addScriptedField( + name: string, + script: string, + fieldType: string = 'string', + lang: string = 'painless' + ) { const scriptedFields = this.getScriptedFields(); const names = _.map(scriptedFields, 'name'); @@ -377,27 +247,24 @@ export class IndexPattern implements IIndexPattern { throw new DuplicateField(name); } - try { - this.fields.add({ - name, - script, - type: fieldType, - scripted: true, - lang, - aggregatable: true, - searchable: true, - count: 0, - readFromDocValues: false, - }); - } catch (err) { - if (err instanceof FieldTypeUnknownError) { - this.unknownFieldErrorNotification(err.fieldSpec.name, err.fieldSpec.type, this.title); - } else { - throw err; - } - } + this.fields.add({ + name, + script, + type: fieldType, + scripted: true, + lang, + aggregatable: true, + searchable: true, + count: 0, + readFromDocValues: false, + }); } + /** + * Remove scripted field from field list + * @param fieldName + */ + removeScriptedField(fieldName: string) { const field = this.fields.getByName(fieldName); if (field) { @@ -424,9 +291,14 @@ export class IndexPattern implements IIndexPattern { field.count = count; try { - const res = await this.savedObjectsClient.update(savedObjectType, this.id, this.prepBody(), { - version: this.version, - }); + const res = await this.savedObjectsClient.update( + 'index-pattern', + this.id, + this.getAsSavedObjectBody(), + { + version: this.version, + } + ); this.version = res.version; } catch (e) { // no need for an error message here @@ -468,24 +340,48 @@ export class IndexPattern implements IIndexPattern { return this.typeMeta?.aggs; } - isWildcard() { + /** + * Does this index pattern title include a '*' + */ + private isWildcard() { return _.includes(this.title, '*'); } - prepBody() { + /** + * Returns index pattern as saved object body for saving + */ + getAsSavedObjectBody() { + const serializeFieldFormatMap = ( + flat: any, + format: FieldFormat | undefined, + field: string | undefined + ) => { + if (format && field) { + flat[field] = format; + } + }; + const serialized = _.transform(this.fieldFormatMap, serializeFieldFormatMap); + const fieldFormatMap = _.isEmpty(serialized) ? undefined : JSON.stringify(serialized); + return { title: this.title, timeFieldName: this.timeFieldName, intervalName: this.intervalName, - sourceFilters: this.mapping.sourceFilters._serialize!(this.sourceFilters), - fields: this.mapping.fields._serialize!(this.fields), - fieldFormatMap: this.mapping.fieldFormatMap._serialize!(this.fieldFormatMap), + sourceFilters: this.sourceFilters ? JSON.stringify(this.sourceFilters) : undefined, + fields: this.fields ? JSON.stringify(this.fields) : undefined, + fieldFormatMap, type: this.type, - typeMeta: this.mapping.typeMeta._serialize!(this.typeMeta), + typeMeta: this.typeMeta ? JSON.stringify(this.typeMeta) : undefined, }; } - getFormatterForField(field: IndexPatternField | IndexPatternField['spec']): FieldFormat { + /** + * Provide a field, get its formatter + * @param field + */ + getFormatterForField( + field: IndexPatternField | IndexPatternField['spec'] | IFieldType + ): FieldFormat { return ( this.fieldFormatMap[field.name] || this.fieldFormats.getDefaultInstance( @@ -494,81 +390,4 @@ export class IndexPattern implements IIndexPattern { ) ); } - - async create(allowOverride: boolean = false) { - const _create = async (duplicateId?: string) => { - if (duplicateId) { - this.patternCache.clear(duplicateId); - await this.savedObjectsClient.delete(savedObjectType, duplicateId); - } - - const body = this.prepBody(); - const response = await this.savedObjectsClient.create(savedObjectType, body, { id: this.id }); - - this.id = response.id; - return response.id; - }; - - const potentialDuplicateByTitle = await findByTitle(this.savedObjectsClient, this.title); - // If there is potentially duplicate title, just create it - if (!potentialDuplicateByTitle) { - return await _create(); - } - - // We found a duplicate but we aren't allowing override, show the warn modal - if (!allowOverride) { - return false; - } - - return await _create(potentialDuplicateByTitle.id); - } - - async _fetchFields() { - const fields = await this.fieldsFetcher.fetch(this); - const scripted = this.getScriptedFields().map((field) => field.spec); - try { - this.fields.replaceAll([...fields, ...scripted]); - } catch (err) { - if (err instanceof FieldTypeUnknownError) { - this.unknownFieldErrorNotification(err.fieldSpec.name, err.fieldSpec.type, this.title); - } else { - throw err; - } - } - } - - refreshFields() { - return ( - this._fetchFields() - // todo - .then(() => this.indexPatternsService.save(this)) - .catch((err) => { - // https://github.com/elastic/kibana/issues/9224 - // This call will attempt to remap fields from the matching - // ES index which may not actually exist. In that scenario, - // we still want to notify the user that there is a problem - // but we do not want to potentially make any pages unusable - // so do not rethrow the error here - - if (err instanceof IndexPatternMissingIndices) { - this.onNotification({ - title: (err as any).message, - color: 'danger', - iconType: 'alert', - }); - return []; - } - - this.onError(err, { - title: i18n.translate('data.indexPatterns.fetchFieldErrorTitle', { - defaultMessage: 'Error fetching fields for index pattern {title} (ID: {id})', - values: { - id: this.id, - title: this.title, - }, - }), - }); - }) - ); - } } 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 d3b3a73a4b50f..b22437ebbdb4e 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 @@ -18,7 +18,7 @@ */ import { defaults } from 'lodash'; -import { IndexPatternsService } from '.'; +import { IndexPatternsService, IndexPattern } from '.'; import { fieldFormatsMock } from '../../field_formats/mocks'; import { stubbedSavedObjectIndexPattern } from '../../../../../fixtures/stubbed_saved_object_index_pattern'; import { UiSettingsCommon, SavedObjectsClientCommon, SavedObject } from '../types'; @@ -31,7 +31,6 @@ const createFieldsFetcher = jest.fn().mockImplementation(() => ({ })); const fieldFormats = fieldFormatsMock; - let object: any = {}; function setDocsourcePayload(id: string | null, providedPayload: any) { @@ -43,16 +42,18 @@ describe('IndexPatterns', () => { let savedObjectsClient: SavedObjectsClientCommon; beforeEach(() => { + const indexPatternObj = { id: 'id', version: 'a', attributes: { title: 'title' } }; savedObjectsClient = {} as SavedObjectsClientCommon; savedObjectsClient.find = jest.fn( - () => - Promise.resolve([{ id: 'id', attributes: { title: 'title' } }]) as Promise< - Array> - > + () => Promise.resolve([indexPatternObj]) as Promise>> ); savedObjectsClient.delete = jest.fn(() => Promise.resolve({}) as Promise); - savedObjectsClient.get = jest.fn().mockImplementation(() => object); savedObjectsClient.create = jest.fn(); + savedObjectsClient.get = jest.fn().mockImplementation(async (type, id) => ({ + id: object.id, + version: object.version, + attributes: object.attributes, + })); savedObjectsClient.update = jest .fn() .mockImplementation(async (type, id, body, { version }) => { @@ -141,30 +142,73 @@ describe('IndexPatterns', () => { }); // Create a normal index patterns - const pattern = await indexPatterns.make('foo'); + const pattern = await indexPatterns.get('foo'); expect(pattern.version).toBe('fooa'); + indexPatterns.clearCache(); // Create the same one - we're going to handle concurrency - const samePattern = await indexPatterns.make('foo'); + const samePattern = await indexPatterns.get('foo'); expect(samePattern.version).toBe('fooaa'); // This will conflict because samePattern did a save (from refreshFields) // but the resave should work fine pattern.title = 'foo2'; - await indexPatterns.save(pattern); + await indexPatterns.updateSavedObject(pattern); // This should not be able to recover samePattern.title = 'foo3'; let result; try { - await indexPatterns.save(samePattern); + await indexPatterns.updateSavedObject(samePattern); } catch (err) { result = err; } expect(result.res.status).toBe(409); }); + + test('create', async () => { + const title = 'kibana-*'; + indexPatterns.refreshFields = jest.fn(); + + const indexPattern = await indexPatterns.create({ title }, true); + expect(indexPattern).toBeInstanceOf(IndexPattern); + expect(indexPattern.title).toBe(title); + expect(indexPatterns.refreshFields).not.toBeCalled(); + + await indexPatterns.create({ title }); + expect(indexPatterns.refreshFields).toBeCalled(); + }); + + test('createAndSave', async () => { + const title = 'kibana-*'; + indexPatterns.createSavedObject = jest.fn(); + indexPatterns.setDefault = jest.fn(); + await indexPatterns.createAndSave({ title }); + expect(indexPatterns.createSavedObject).toBeCalled(); + expect(indexPatterns.setDefault).toBeCalled(); + }); + + test('savedObjectToSpec', () => { + const savedObject = { + id: 'id', + version: 'version', + attributes: { + title: 'kibana-*', + timeFieldName: '@timestamp', + fields: '[]', + sourceFilters: '[{"value":"item1"},{"value":"item2"}]', + fieldFormatMap: '{"field":{}}', + typeMeta: '{}', + type: '', + }, + type: 'index-pattern', + references: [], + }; + + expect(indexPatterns.savedObjectToSpec(savedObject)).toMatchSnapshot(); + }); }); 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 47484f8ec75bb..eef8ef10ea754 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 @@ -33,9 +33,17 @@ import { IIndexPatternsApiClient, GetFieldsOptions, IndexPatternSpec, + IndexPatternAttributes, + FieldSpec, + FieldFormatMap, + IndexPatternFieldMap, } from '../types'; import { FieldFormatsStartCommon } from '../../field_formats'; import { UI_SETTINGS, SavedObject } from '../../../common'; +import { SavedObjectNotFound } from '../../../../kibana_utils/common'; +import { IndexPatternMissingIndices } from '../lib'; +import { findByTitle } from '../utils'; +import { DuplicateIndexPatternError } from '../errors'; const indexPatternCache = createIndexPatternCache(); const MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS = 3; @@ -86,6 +94,9 @@ export class IndexPatternsService { ); } + /** + * Refresh cache of index pattern ids and titles + */ private async refreshSavedObjectsCache() { this.savedObjectsCache = await this.savedObjectsClient.find({ type: 'index-pattern', @@ -94,6 +105,10 @@ export class IndexPatternsService { }); } + /** + * Get list of index pattern ids + * @param refresh Force refresh of index pattern list + */ getIds = async (refresh: boolean = false) => { if (!this.savedObjectsCache || refresh) { await this.refreshSavedObjectsCache(); @@ -104,6 +119,10 @@ export class IndexPatternsService { return this.savedObjectsCache.map((obj) => obj?.id); }; + /** + * Get list of index pattern titles + * @param refresh Force refresh of index pattern list + */ getTitles = async (refresh: boolean = false): Promise => { if (!this.savedObjectsCache || refresh) { await this.refreshSavedObjectsCache(); @@ -114,14 +133,29 @@ export class IndexPatternsService { return this.savedObjectsCache.map((obj) => obj?.attributes?.title); }; - getFieldsForTimePattern = (options: GetFieldsOptions = {}) => { - return this.apiClient.getFieldsForTimePattern(options); - }; - - getFieldsForWildcard = (options: GetFieldsOptions = {}) => { - return this.apiClient.getFieldsForWildcard(options); + /** + * Get list of index pattern ids with titles + * @param refresh Force refresh of index pattern list + */ + getIdsWithTitle = async ( + refresh: boolean = false + ): Promise> => { + if (!this.savedObjectsCache || refresh) { + await this.refreshSavedObjectsCache(); + } + if (!this.savedObjectsCache) { + return []; + } + return this.savedObjectsCache.map((obj) => ({ + id: obj?.id, + title: obj?.attributes?.title, + })); }; + /** + * Clear index pattern list cache + * @param id optionally clear a single id + */ clearCache = (id?: string) => { this.savedObjectsCache = null; if (id) { @@ -130,6 +164,7 @@ export class IndexPatternsService { indexPatternCache.clearAll(); } }; + getCache = async () => { if (!this.savedObjectsCache) { await this.refreshSavedObjectsCache(); @@ -137,6 +172,9 @@ export class IndexPatternsService { return this.savedObjectsCache; }; + /** + * Get default index pattern + */ getDefault = async () => { const defaultIndexPatternId = await this.config.get('defaultIndex'); if (defaultIndexPatternId) { @@ -146,47 +184,350 @@ export class IndexPatternsService { return null; }; + /** + * Optionally set default index pattern, unless force = true + * @param id + * @param force + */ + setDefault = async (id: string, force = false) => { + if (force || !this.config.get('defaultIndex')) { + await this.config.set('defaultIndex', id); + } + }; + + private isFieldRefreshRequired(specs?: IndexPatternFieldMap): boolean { + if (!specs) { + return true; + } + + return Object.values(specs).every((spec) => { + // See https://github.com/elastic/kibana/pull/8421 + const hasFieldCaps = 'aggregatable' in spec && 'searchable' in spec; + + // See https://github.com/elastic/kibana/pull/11969 + const hasDocValuesFlag = 'readFromDocValues' in spec; + + return !hasFieldCaps || !hasDocValuesFlag; + }); + } + + /** + * Get field list by providing { pattern } + * @param options + */ + getFieldsForWildcard = async (options: GetFieldsOptions = {}) => { + const metaFields = await this.config.get(UI_SETTINGS.META_FIELDS); + return this.apiClient.getFieldsForWildcard({ + pattern: options.pattern, + metaFields, + type: options.type, + params: options.params || {}, + }); + }; + + /** + * Get field list by providing an index patttern (or spec) + * @param options + */ + getFieldsForIndexPattern = async ( + indexPattern: IndexPattern | IndexPatternSpec, + options: GetFieldsOptions = {} + ) => + this.getFieldsForWildcard({ + pattern: indexPattern.title as string, + ...options, + type: indexPattern.type, + params: indexPattern.typeMeta && indexPattern.typeMeta.params, + }); + + /** + * Refresh field list for a given index pattern + * @param indexPattern + */ + refreshFields = async (indexPattern: IndexPattern) => { + try { + const fields = await this.getFieldsForIndexPattern(indexPattern); + const scripted = indexPattern.getScriptedFields().map((field) => field.spec); + indexPattern.fields.replaceAll([...fields, ...scripted]); + } catch (err) { + if (err instanceof IndexPatternMissingIndices) { + this.onNotification({ title: (err as any).message, color: 'danger', iconType: 'alert' }); + } + + this.onError(err, { + title: i18n.translate('data.indexPatterns.fetchFieldErrorTitle', { + defaultMessage: 'Error fetching fields for index pattern {title} (ID: {id})', + values: { id: indexPattern.id, title: indexPattern.title }, + }), + }); + } + }; + + /** + * Refreshes a field list from a spec before an index pattern instance is created + * @param fields + * @param id + * @param title + * @param options + */ + private refreshFieldSpecMap = async ( + fields: IndexPatternFieldMap, + id: string, + title: string, + options: GetFieldsOptions + ) => { + const scriptdFields = Object.values(fields).filter((field) => field.scripted); + try { + const newFields = await this.getFieldsForWildcard(options); + return this.fieldArrayToMap([...newFields, ...scriptdFields]); + } catch (err) { + if (err instanceof IndexPatternMissingIndices) { + this.onNotification({ title: (err as any).message, color: 'danger', iconType: 'alert' }); + return {}; + } + + this.onError(err, { + title: i18n.translate('data.indexPatterns.fetchFieldErrorTitle', { + defaultMessage: 'Error fetching fields for index pattern {title} (ID: {id})', + values: { id, title }, + }), + }); + } + return fields; + }; + + /** + * Applies a set of formats to a set of fields + * @param fieldSpecs + * @param fieldFormatMap + */ + private addFormatsToFields = (fieldSpecs: FieldSpec[], fieldFormatMap: FieldFormatMap) => { + Object.entries(fieldFormatMap).forEach(([fieldName, value]) => { + const field = fieldSpecs.find((fld: FieldSpec) => fld.name === fieldName); + if (field) { + field.format = value; + } + }); + }; + + /** + * Converts field array to map + * @param fields + */ + fieldArrayToMap = (fields: FieldSpec[]) => + fields.reduce((collector, field) => { + collector[field.name] = field; + return collector; + }, {}); + + /** + * Converts index pattern saved object to index pattern spec + * @param savedObject + */ + + savedObjectToSpec = (savedObject: SavedObject): IndexPatternSpec => { + const { + id, + version, + attributes: { + title, + timeFieldName, + intervalName, + fields, + sourceFilters, + fieldFormatMap, + typeMeta, + type, + }, + } = savedObject; + + const parsedSourceFilters = sourceFilters ? JSON.parse(sourceFilters) : undefined; + const parsedTypeMeta = typeMeta ? JSON.parse(typeMeta) : undefined; + const parsedFieldFormatMap = fieldFormatMap ? JSON.parse(fieldFormatMap) : {}; + const parsedFields: FieldSpec[] = fields ? JSON.parse(fields) : []; + + this.addFormatsToFields(parsedFields, parsedFieldFormatMap); + return { + id, + version, + title, + intervalName, + timeFieldName, + sourceFilters: parsedSourceFilters, + fields: this.fieldArrayToMap(parsedFields), + typeMeta: parsedTypeMeta, + type, + }; + }; + + /** + * Get an index pattern by id. Cache optimized + * @param id + */ + get = async (id: string): Promise => { const cache = indexPatternCache.get(id); if (cache) { return cache; } - const indexPattern = await this.make(id); + const savedObject = await this.savedObjectsClient.get( + savedObjectType, + id + ); + + if (!savedObject.version) { + throw new SavedObjectNotFound(savedObjectType, id, 'management/kibana/indexPatterns'); + } + + const spec = this.savedObjectToSpec(savedObject); + const { title, type, typeMeta } = spec; + const parsedFieldFormats: FieldFormatMap = savedObject.attributes.fieldFormatMap + ? JSON.parse(savedObject.attributes.fieldFormatMap) + : {}; + + const isFieldRefreshRequired = this.isFieldRefreshRequired(spec.fields); + let isSaveRequired = isFieldRefreshRequired; + try { + spec.fields = isFieldRefreshRequired + ? await this.refreshFieldSpecMap(spec.fields || {}, id, spec.title as string, { + pattern: title, + metaFields: await this.config.get(UI_SETTINGS.META_FIELDS), + type, + params: typeMeta && typeMeta.params, + }) + : spec.fields; + } catch (err) { + isSaveRequired = false; + if (err instanceof IndexPatternMissingIndices) { + this.onNotification({ + title: (err as any).message, + color: 'danger', + iconType: 'alert', + }); + } else { + this.onError(err, { + title: i18n.translate('data.indexPatterns.fetchFieldErrorTitle', { + defaultMessage: 'Error fetching fields for index pattern {title} (ID: {id})', + values: { id, title }, + }), + }); + } + } + + Object.entries(parsedFieldFormats).forEach(([fieldName, value]) => { + const field = spec.fields?.[fieldName]; + if (field) { + field.format = value; + } + }); + + const indexPattern = await this.create(spec, true); + indexPatternCache.set(id, indexPattern); + if (isSaveRequired) { + try { + this.updateSavedObject(indexPattern); + } catch (err) { + this.onError(err, { + title: i18n.translate('data.indexPatterns.fetchFieldSaveErrorTitle', { + defaultMessage: + 'Error saving after fetching fields for index pattern {title} (ID: {id})', + values: { + id: indexPattern.id, + title: indexPattern.title, + }, + }), + }); + } + } - return indexPatternCache.set(id, indexPattern); + indexPattern.resetOriginalSavedObjectBody(); + return indexPattern; }; - async specToIndexPattern(spec: IndexPatternSpec) { + /** + * Create a new index pattern instance + * @param spec + * @param skipFetchFields + */ + async create(spec: IndexPatternSpec, skipFetchFields = false): Promise { const shortDotsEnable = await this.config.get(UI_SETTINGS.SHORT_DOTS_ENABLE); const metaFields = await this.config.get(UI_SETTINGS.META_FIELDS); - const indexPattern = new IndexPattern(spec.id, { + const indexPattern = new IndexPattern({ + spec, savedObjectsClient: this.savedObjectsClient, - apiClient: this.apiClient, - patternCache: indexPatternCache, fieldFormats: this.fieldFormats, - indexPatternsService: this, - onNotification: this.onNotification, - onError: this.onError, shortDotsEnable, metaFields, }); - indexPattern.initFromSpec(spec); + if (!skipFetchFields) { + await this.refreshFields(indexPattern); + } + + return indexPattern; + } + + /** + * Create a new index pattern and save it right away + * @param spec + * @param override Overwrite if existing index pattern exists + * @param skipFetchFields + */ + + async createAndSave(spec: IndexPatternSpec, override = false, skipFetchFields = false) { + const indexPattern = await this.create(spec, skipFetchFields); + await this.createSavedObject(indexPattern, override); + await this.setDefault(indexPattern.id as string); + return indexPattern; + } + + /** + * Save a new index pattern + * @param indexPattern + * @param override Overwrite if existing index pattern exists + */ + + async createSavedObject(indexPattern: IndexPattern, 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}`); + } + } + + const body = indexPattern.getAsSavedObjectBody(); + const response = await this.savedObjectsClient.create(savedObjectType, body, { + id: indexPattern.id, + }); + indexPattern.id = response.id; + indexPatternCache.set(indexPattern.id, indexPattern); return indexPattern; } - async save(indexPattern: IndexPattern, saveAttempts: number = 0): Promise { + /** + * Save existing index pattern. Will attempt to merge differences if there are conflicts + * @param indexPattern + * @param saveAttempts + */ + + async updateSavedObject( + indexPattern: IndexPattern, + saveAttempts: number = 0 + ): Promise { if (!indexPattern.id) return; - const shortDotsEnable = await this.config.get(UI_SETTINGS.SHORT_DOTS_ENABLE); - const metaFields = await this.config.get(UI_SETTINGS.META_FIELDS); - const body = indexPattern.prepBody(); + // get the list of attributes + const body = indexPattern.getAsSavedObjectBody(); + const originalBody = indexPattern.getOriginalSavedObjectBody(); + // get changed keys const originalChangedKeys: string[] = []; Object.entries(body).forEach(([key, value]) => { - if (value !== indexPattern.originalBody[key]) { + if (value !== (originalBody as any)[key]) { originalChangedKeys.push(key); } }); @@ -197,92 +538,60 @@ export class IndexPatternsService { indexPattern.id = resp.id; indexPattern.version = resp.version; }) - .catch((err) => { + .catch(async (err) => { if (err?.res?.status === 409 && saveAttempts++ < MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS) { - const samePattern = new IndexPattern(indexPattern.id, { - savedObjectsClient: this.savedObjectsClient, - apiClient: this.apiClient, - patternCache: indexPatternCache, - fieldFormats: this.fieldFormats, - indexPatternsService: this, - onNotification: this.onNotification, - onError: this.onError, - shortDotsEnable, - metaFields, + const samePattern = await this.get(indexPattern.id as string); + // What keys changed from now and what the server returned + const updatedBody = samePattern.getAsSavedObjectBody(); + + // Build a list of changed keys from the server response + // and ensure we ignore the key if the server response + // is the same as the original response (since that is expected + // if we made a change in that key) + + const serverChangedKeys: string[] = []; + Object.entries(updatedBody).forEach(([key, value]) => { + if (value !== (body as any)[key] && value !== (originalBody as any)[key]) { + serverChangedKeys.push(key); + } }); - return samePattern.init().then(() => { - // What keys changed from now and what the server returned - const updatedBody = samePattern.prepBody(); - - // Build a list of changed keys from the server response - // and ensure we ignore the key if the server response - // is the same as the original response (since that is expected - // if we made a change in that key) - - const serverChangedKeys: string[] = []; - Object.entries(updatedBody).forEach(([key, value]) => { - if (value !== (body as any)[key] && value !== indexPattern.originalBody[key]) { - serverChangedKeys.push(key); - } - }); - - let unresolvedCollision = false; - for (const originalKey of originalChangedKeys) { - for (const serverKey of serverChangedKeys) { - if (originalKey === serverKey) { - unresolvedCollision = true; - break; - } + let unresolvedCollision = false; + for (const originalKey of originalChangedKeys) { + for (const serverKey of serverChangedKeys) { + if (originalKey === serverKey) { + unresolvedCollision = true; + break; } } + } - if (unresolvedCollision) { - const title = i18n.translate('data.indexPatterns.unableWriteLabel', { - defaultMessage: - 'Unable to write index pattern! Refresh the page to get the most up to date changes for this index pattern.', - }); - - this.onNotification({ title, color: 'danger' }); - throw err; - } - - // Set the updated response on this object - serverChangedKeys.forEach((key) => { - (indexPattern as any)[key] = (samePattern as any)[key]; + if (unresolvedCollision) { + const title = i18n.translate('data.indexPatterns.unableWriteLabel', { + defaultMessage: + 'Unable to write index pattern! Refresh the page to get the most up to date changes for this index pattern.', }); - indexPattern.version = samePattern.version; - // Clear cache - indexPatternCache.clear(indexPattern.id!); + this.onNotification({ title, color: 'danger' }); + throw err; + } - // Try the save again - return this.save(indexPattern, saveAttempts); + // Set the updated response on this object + serverChangedKeys.forEach((key) => { + (indexPattern as any)[key] = (samePattern as any)[key]; }); + indexPattern.version = samePattern.version; + + // Clear cache + indexPatternCache.clear(indexPattern.id!); + + // Try the save again + return this.updateSavedObject(indexPattern, saveAttempts); } throw err; }); } - async make(id?: string): Promise { - const shortDotsEnable = await this.config.get(UI_SETTINGS.SHORT_DOTS_ENABLE); - const metaFields = await this.config.get(UI_SETTINGS.META_FIELDS); - - const indexPattern = new IndexPattern(id, { - savedObjectsClient: this.savedObjectsClient, - apiClient: this.apiClient, - patternCache: indexPatternCache, - fieldFormats: this.fieldFormats, - indexPatternsService: this, - onNotification: this.onNotification, - onError: this.onError, - shortDotsEnable, - metaFields, - }); - - return indexPattern.init(); - } - /** * Deletes an index pattern from .kibana index * @param indexPatternId: Id of kibana Index Pattern to delete diff --git a/src/plugins/data/common/index_patterns/types.ts b/src/plugins/data/common/index_patterns/types.ts index 7a230c20f6cd0..cb0c3aa0de38e 100644 --- a/src/plugins/data/common/index_patterns/types.ts +++ b/src/plugins/data/common/index_patterns/types.ts @@ -22,29 +22,23 @@ import { ToastInputFields, ErrorToastOptions } from 'src/core/public/notificatio import type { SavedObject } from 'src/core/server'; import { IFieldType } from './fields'; import { SerializedFieldFormat } from '../../../expressions/common'; -import { KBN_FIELD_TYPES } from '..'; +import { KBN_FIELD_TYPES, IndexPatternField, FieldFormat } from '..'; + +export type FieldFormatMap = Record; export interface IIndexPattern { - [key: string]: any; fields: IFieldType[]; title: string; id?: string; type?: string; timeFieldName?: string; getTimeField?(): IFieldType | undefined; - fieldFormatMap?: Record< - string, - { - id: string; - params: unknown; - } - >; + fieldFormatMap?: Record | undefined>; + getFormatterForField?: ( + field: IndexPatternField | IndexPatternField['spec'] | IFieldType + ) => FieldFormat; } -/** - * Use data plugin interface instead - * @deprecated - */ export interface IndexPatternAttributes { type: string; fields: string; @@ -166,15 +160,18 @@ export interface FieldSpec { indexed?: boolean; } +export type IndexPatternFieldMap = Record; + export interface IndexPatternSpec { id?: string; version?: string; - - title: string; + title?: string; + intervalName?: string; timeFieldName?: string; sourceFilters?: SourceFilter[]; - fields?: FieldSpec[]; + fields?: IndexPatternFieldMap; typeMeta?: TypeMeta; + type?: string; } export interface SourceFilter { diff --git a/src/plugins/data/common/search/aggs/buckets/histogram.ts b/src/plugins/data/common/search/aggs/buckets/histogram.ts index 4b631e1fd7cd7..c3d3f041dd0c7 100644 --- a/src/plugins/data/common/search/aggs/buckets/histogram.ts +++ b/src/plugins/data/common/search/aggs/buckets/histogram.ts @@ -158,6 +158,7 @@ export const getHistogramBucketAgg = ({ maxBucketsUiSettings: getConfig(UI_SETTINGS.HISTOGRAM_MAX_BARS), maxBucketsUserInput: aggConfig.params.maxBars, intervalBase: aggConfig.params.intervalBase, + esTypes: aggConfig.params.field?.spec?.esTypes || [], }); }, }, diff --git a/src/plugins/data/common/search/aggs/buckets/lib/histogram_calculate_interval.test.ts b/src/plugins/data/common/search/aggs/buckets/lib/histogram_calculate_interval.test.ts index d3a95b32cd425..7e5e20e5917aa 100644 --- a/src/plugins/data/common/search/aggs/buckets/lib/histogram_calculate_interval.test.ts +++ b/src/plugins/data/common/search/aggs/buckets/lib/histogram_calculate_interval.test.ts @@ -21,6 +21,7 @@ import { calculateHistogramInterval, CalculateHistogramIntervalParams, } from './histogram_calculate_interval'; +import { ES_FIELD_TYPES } from '../../../../types'; describe('calculateHistogramInterval', () => { describe('auto calculating mode', () => { @@ -36,10 +37,91 @@ describe('calculateHistogramInterval', () => { min: 0, max: 1, }, + esTypes: [], }; }); describe('maxBucketsUserInput is defined', () => { + test('should set 1 as an interval for integer numbers that are less than maxBuckets #1', () => { + const p = { + ...params, + maxBucketsUserInput: 100, + values: { + min: 1, + max: 50, + }, + esTypes: [ES_FIELD_TYPES.INTEGER], + }; + expect(calculateHistogramInterval(p)).toEqual(1); + }); + + test('should set 1 as an interval for integer numbers that are less than maxBuckets #2', () => { + const p = { + ...params, + maxBucketsUiSettings: 1000, + maxBucketsUserInput: 258, + values: { + min: 521, + max: 689, + }, + esTypes: [ES_FIELD_TYPES.INTEGER], + }; + expect(calculateHistogramInterval(p)).toEqual(1); + }); + + test('should set correct interval for integer numbers that are greater than maxBuckets #1', () => { + const p = { + ...params, + maxBucketsUserInput: 100, + values: { + min: 400, + max: 790, + }, + esTypes: [ES_FIELD_TYPES.INTEGER, ES_FIELD_TYPES.SHORT], + }; + expect(calculateHistogramInterval(p)).toEqual(5); + }); + + test('should set correct interval for integer numbers that are greater than maxBuckets #2', () => { + // diff === 3456211; interval === 50000; buckets === 69 + const p = { + ...params, + maxBucketsUserInput: 100, + values: { + min: 567, + max: 3456778, + }, + esTypes: [ES_FIELD_TYPES.LONG], + }; + expect(calculateHistogramInterval(p)).toEqual(50000); + }); + + test('should not set integer interval if the field type is float #1', () => { + const p = { + ...params, + maxBucketsUserInput: 100, + values: { + min: 0, + max: 1, + }, + esTypes: [ES_FIELD_TYPES.FLOAT], + }; + expect(calculateHistogramInterval(p)).toEqual(0.01); + }); + + test('should not set integer interval if the field type is float #2', () => { + const p = { + ...params, + maxBucketsUserInput: 100, + values: { + min: 0, + max: 1, + }, + esTypes: [ES_FIELD_TYPES.INTEGER, ES_FIELD_TYPES.FLOAT], + }; + expect(calculateHistogramInterval(p)).toEqual(0.01); + }); + test('should not set interval which more than largest possible', () => { const p = { ...params, @@ -48,6 +130,7 @@ describe('calculateHistogramInterval', () => { min: 150, max: 250, }, + esTypes: [ES_FIELD_TYPES.SHORT], }; expect(calculateHistogramInterval(p)).toEqual(1); }); @@ -61,6 +144,7 @@ describe('calculateHistogramInterval', () => { min: 0.1, max: 0.9, }, + esTypes: [ES_FIELD_TYPES.FLOAT], }) ).toBe(0.02); }); @@ -74,6 +158,7 @@ describe('calculateHistogramInterval', () => { min: 10.45, max: 1000.05, }, + esTypes: [ES_FIELD_TYPES.FLOAT], }) ).toBe(100); }); @@ -88,6 +173,7 @@ describe('calculateHistogramInterval', () => { min: 0, max: 100, }, + esTypes: [ES_FIELD_TYPES.BYTE], }) ).toEqual(1); }); @@ -100,8 +186,9 @@ describe('calculateHistogramInterval', () => { min: 1, max: 10, }, + esTypes: [ES_FIELD_TYPES.INTEGER], }) - ).toEqual(0.1); + ).toEqual(1); }); test('should set intervals for integer numbers (diff more than maxBucketsUiSettings)', () => { @@ -113,6 +200,7 @@ describe('calculateHistogramInterval', () => { min: 45678, max: 90123, }, + esTypes: [ES_FIELD_TYPES.INTEGER], }) ).toEqual(500); }); @@ -127,6 +215,7 @@ describe('calculateHistogramInterval', () => { min: 1.245, max: 2.9, }, + esTypes: [ES_FIELD_TYPES.FLOAT], }) ).toEqual(0.02); expect( @@ -136,6 +225,7 @@ describe('calculateHistogramInterval', () => { min: 0.5, max: 2.3, }, + esTypes: [ES_FIELD_TYPES.FLOAT], }) ).toEqual(0.02); }); @@ -149,6 +239,7 @@ describe('calculateHistogramInterval', () => { min: 0.1, max: 0.9, }, + esTypes: [ES_FIELD_TYPES.FLOAT], }) ).toBe(0.01); }); diff --git a/src/plugins/data/common/search/aggs/buckets/lib/histogram_calculate_interval.ts b/src/plugins/data/common/search/aggs/buckets/lib/histogram_calculate_interval.ts index 378340e876296..313ecf1000f41 100644 --- a/src/plugins/data/common/search/aggs/buckets/lib/histogram_calculate_interval.ts +++ b/src/plugins/data/common/search/aggs/buckets/lib/histogram_calculate_interval.ts @@ -18,6 +18,7 @@ */ import { isAutoInterval } from '../_interval_options'; +import { ES_FIELD_TYPES } from '../../../../types'; interface IntervalValuesRange { min: number; @@ -28,6 +29,7 @@ export interface CalculateHistogramIntervalParams { interval: string; maxBucketsUiSettings: number; maxBucketsUserInput?: number | ''; + esTypes: ES_FIELD_TYPES[]; intervalBase?: number; values?: IntervalValuesRange; } @@ -77,11 +79,27 @@ const calculateForGivenInterval = ( - The lower power of 10, times 2 - The lower power of 10, times 5 **/ -const calculateAutoInterval = (diff: number, maxBars: number) => { +const calculateAutoInterval = (diff: number, maxBars: number, esTypes: ES_FIELD_TYPES[]) => { const exactInterval = diff / maxBars; - const lowerPower = Math.pow(10, Math.floor(Math.log10(exactInterval))); + // For integer fields that are less than maxBars, we should use 1 as the value of interval + // Elastic has 4 integer data types: long, integer, short, byte + // see: https://www.elastic.co/guide/en/elasticsearch/reference/current/number.html + if ( + diff < maxBars && + esTypes.every((esType) => + [ + ES_FIELD_TYPES.INTEGER, + ES_FIELD_TYPES.LONG, + ES_FIELD_TYPES.SHORT, + ES_FIELD_TYPES.BYTE, + ].includes(esType) + ) + ) { + return 1; + } + const lowerPower = Math.pow(10, Math.floor(Math.log10(exactInterval))); const autoBuckets = diff / lowerPower; if (autoBuckets > maxBars) { @@ -103,6 +121,7 @@ export const calculateHistogramInterval = ({ maxBucketsUserInput, intervalBase, values, + esTypes, }: CalculateHistogramIntervalParams) => { const isAuto = isAutoInterval(interval); let calculatedInterval = isAuto ? 0 : parseFloat(interval); @@ -119,8 +138,10 @@ export const calculateHistogramInterval = ({ calculatedInterval = isAuto ? calculateAutoInterval( diff, + // Mind maxBucketsUserInput can be an empty string, hence we need to ensure it here - Math.min(maxBucketsUiSettings, maxBucketsUserInput || maxBucketsUiSettings) + Math.min(maxBucketsUiSettings, maxBucketsUserInput || maxBucketsUiSettings), + esTypes ) : calculateForGivenInterval(diff, calculatedInterval, maxBucketsUiSettings); } diff --git a/src/plugins/data/common/search/es_search/index.ts b/src/plugins/data/common/search/es_search/index.ts index d8f7b5091eb8f..8e8897c7d7517 100644 --- a/src/plugins/data/common/search/es_search/index.ts +++ b/src/plugins/data/common/search/es_search/index.ts @@ -18,3 +18,4 @@ */ export * from './types'; +export * from './utils'; diff --git a/src/legacy/ui/ui_exports/ui_export_types/reduce/flat_concat_values_at_type.js b/src/plugins/data/common/search/es_search/utils.ts similarity index 54% rename from src/legacy/ui/ui_exports/ui_export_types/reduce/flat_concat_values_at_type.js rename to src/plugins/data/common/search/es_search/utils.ts index 229c5be24aac5..517a0c03cf5c8 100644 --- a/src/legacy/ui/ui_exports/ui_export_types/reduce/flat_concat_values_at_type.js +++ b/src/plugins/data/common/search/es_search/utils.ts @@ -17,14 +17,25 @@ * under the License. */ -import { createTypeReducer, flatConcat, mergeWith } from './lib'; +import { IEsSearchResponse } from './types'; /** - * Reducer that merges specs by concatenating the values of - * all keys in accumulator and spec with the same logic as concat - * @param {[type]} initial [description] - * @return {[type]} [description] + * @returns true if response had an error while executing in ES */ -export const flatConcatValuesAtType = createTypeReducer((objectA, objectB) => - mergeWith(objectA || {}, objectB || {}, flatConcat) -); +export const isErrorResponse = (response?: IEsSearchResponse) => { + return !response || (!response.isRunning && response.isPartial); +}; + +/** + * @returns true if response is completed successfully + */ +export const isCompleteResponse = (response?: IEsSearchResponse) => { + return response && !response.isRunning && !response.isPartial; +}; + +/** + * @returns true if request is still running an/d response contains partial results + */ +export const isPartialResponse = (response?: IEsSearchResponse) => { + return response && response.isRunning && response.isPartial; +}; diff --git a/src/plugins/data/common/search/index.ts b/src/plugins/data/common/search/index.ts index 2ee0db384cf06..2ec4afbc60d96 100644 --- a/src/plugins/data/common/search/index.ts +++ b/src/plugins/data/common/search/index.ts @@ -23,3 +23,4 @@ export * from './expressions'; export * from './search_source'; export * from './tabify'; export * from './types'; +export * from './es_search'; diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 5038af9409316..f7dceffa9fdbc 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -230,6 +230,8 @@ import { formatHitProvider, } from './index_patterns'; +export type { IndexPatternsService } from './index_patterns'; + // Index patterns namespace: export const indexPatterns = { ILLEGAL_CHARACTERS_KEY, @@ -262,9 +264,12 @@ export { UI_SETTINGS, TypeMeta as IndexPatternTypeMeta, AggregationRestrictions as IndexPatternAggRestrictions, + IndexPatternSpec, fieldList, } from '../common'; +export { DuplicateIndexPatternError } from '../common/index_patterns/errors'; + /* * Autocomplete query suggestions: */ @@ -374,7 +379,7 @@ export { export type { SearchSource } from './search'; -export { ISearchOptions } from '../common'; +export { ISearchOptions, isErrorResponse, isCompleteResponse, isPartialResponse } from '../common'; // Search namespace export const search = { @@ -415,6 +420,7 @@ export { StatefulSearchBarProps, FilterBar, QueryStringInput, + QueryStringInputProps, IndexPatternSelect, } from './ui'; diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index db8d9dba4e0c7..28dfbf824470c 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -454,6 +454,13 @@ export interface DataPublicPluginStartUi { SearchBar: React.ComponentType; } +// Warning: (ae-missing-release-tag) "DuplicateIndexPatternError" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class DuplicateIndexPatternError extends Error { + constructor(message: string); +} + // @public (undocumented) export enum ES_FIELD_TYPES { // (undocumented) @@ -1004,16 +1011,15 @@ export interface IFieldType { // // @public (undocumented) export interface IIndexPattern { + // Warning: (ae-forgotten-export) The symbol "SerializedFieldFormat" needs to be exported by the entry point index.d.ts + // // (undocumented) - [key: string]: any; - // (undocumented) - fieldFormatMap?: Record; + fieldFormatMap?: Record | undefined>; // (undocumented) fields: IFieldType[]; // (undocumented) + getFormatterForField?: (field: IndexPatternField | IndexPatternField['spec'] | IFieldType) => FieldFormat; + // (undocumented) getTimeField?(): IFieldType | undefined; // (undocumented) id?: string; @@ -1043,10 +1049,12 @@ export interface IIndexPatternFieldList extends Array { removeAll(): void; // (undocumented) replaceAll(specs: FieldSpec[]): void; + // Warning: (ae-forgotten-export) The symbol "IndexPatternFieldMap" needs to be exported by the entry point index.d.ts + // // (undocumented) toSpec(options?: { getFormatterForField?: IndexPattern['getFormatterForField']; - }): FieldSpec[]; + }): IndexPatternFieldMap; // (undocumented) update(field: FieldSpec): void; } @@ -1079,27 +1087,23 @@ export type IMetricAggType = MetricAggType; // @public (undocumented) export class IndexPattern implements IIndexPattern { // Warning: (ae-forgotten-export) The symbol "IndexPatternDeps" needs to be exported by the entry point index.d.ts - constructor(id: string | undefined, { savedObjectsClient, apiClient, patternCache, fieldFormats, indexPatternsService, onNotification, onError, shortDotsEnable, metaFields, }: IndexPatternDeps); - // (undocumented) - addScriptedField(name: string, script: string, fieldType: string | undefined, lang: string): Promise; - // (undocumented) - create(allowOverride?: boolean): Promise; + constructor({ spec, savedObjectsClient, fieldFormats, shortDotsEnable, metaFields, }: IndexPatternDeps); + addScriptedField(name: string, script: string, fieldType?: string, lang?: string): Promise; // (undocumented) - _fetchFields(): Promise; - // (undocumented) - fieldFormatMap: any; + fieldFormatMap: Record; // (undocumented) fields: IIndexPatternFieldList & { - toSpec: () => FieldSpec[]; + toSpec: () => IndexPatternFieldMap; }; // (undocumented) - fieldsFetcher: any; - // (undocumented) - flattenHit: any; + flattenHit: (hit: Record, deep?: boolean) => Record; // (undocumented) - formatField: any; + formatField: FormatFieldFn; // (undocumented) - formatHit: any; + formatHit: { + (hit: Record, type?: string): any; + formatField: FormatFieldFn; + }; // (undocumented) getAggregationRestrictions(): Record> | undefined; + getAsSavedObjectBody(): { + title: string; + timeFieldName: string | undefined; + intervalName: string | undefined; + sourceFilters: string | undefined; + fields: string | undefined; + fieldFormatMap: string | undefined; + type: string | undefined; + typeMeta: string | undefined; + }; // (undocumented) getComputedFields(): { storedFields: string[]; @@ -1120,13 +1134,21 @@ export class IndexPattern implements IIndexPattern { }; // (undocumented) getFieldByName(name: string): IndexPatternField | undefined; - // (undocumented) - getFormatterForField(field: IndexPatternField | IndexPatternField['spec']): FieldFormat; + getFormatterForField(field: IndexPatternField | IndexPatternField['spec'] | IFieldType): FieldFormat; // (undocumented) getNonScriptedFields(): IndexPatternField[]; + getOriginalSavedObjectBody: () => { + title?: string | undefined; + timeFieldName?: string | undefined; + intervalName?: string | undefined; + fields?: string | undefined; + sourceFilters?: string | undefined; + fieldFormatMap?: string | undefined; + typeMeta?: string | undefined; + type?: string | undefined; + }; // (undocumented) getScriptedFields(): IndexPatternField[]; - // (undocumented) getSourceFiltering(): { excludes: any[]; }; @@ -1135,12 +1157,6 @@ export class IndexPattern implements IIndexPattern { // (undocumented) id?: string; // (undocumented) - init(): Promise; - // Warning: (ae-forgotten-export) The symbol "IndexPatternSpec" needs to be exported by the entry point index.d.ts - // - // (undocumented) - initFromSpec(spec: IndexPatternSpec): this; - // (undocumented) intervalName: string | undefined; // (undocumented) isTimeBased(): boolean; @@ -1149,30 +1165,11 @@ export class IndexPattern implements IIndexPattern { // (undocumented) isTimeNanosBased(): boolean; // (undocumented) - isWildcard(): boolean; - // (undocumented) metaFields: string[]; // (undocumented) - originalBody: { - [key: string]: any; - }; - // (undocumented) popularizeField(fieldName: string, unit?: number): Promise; - // (undocumented) - prepBody(): { - title: string; - timeFieldName: string | undefined; - intervalName: string | undefined; - sourceFilters: string | undefined; - fields: string | undefined; - fieldFormatMap: string | undefined; - type: string | undefined; - typeMeta: string | undefined; - }; - // (undocumented) - refreshFields(): Promise; - // (undocumented) removeScriptedField(fieldName: string): void; + resetOriginalSavedObjectBody: () => void; // Warning: (ae-forgotten-export) The symbol "SourceFilter" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -1205,7 +1202,7 @@ export type IndexPatternAggRestrictions = Record | undefined; set conflictDescriptions(conflictDescriptions: Record | undefined); - // (undocumented) get count(): number; set count(count: number); // (undocumented) @@ -1244,14 +1239,12 @@ export class IndexPatternField implements IFieldType { get esTypes(): string[] | undefined; // (undocumented) get filterable(): boolean; - // (undocumented) get lang(): string | undefined; set lang(lang: string | undefined); // (undocumented) get name(): string; // (undocumented) get readFromDocValues(): boolean; - // (undocumented) get script(): string | undefined; set script(script: string | undefined); // (undocumented) @@ -1282,24 +1275,7 @@ export class IndexPatternField implements IFieldType { // (undocumented) toSpec({ getFormatterForField, }?: { getFormatterForField?: IndexPattern['getFormatterForField']; - }): { - count: number; - script: string | undefined; - lang: string | undefined; - conflictDescriptions: Record | undefined; - name: string; - type: string; - esTypes: string[] | undefined; - scripted: boolean; - searchable: boolean; - aggregatable: boolean; - readFromDocValues: boolean; - subType: import("../types").IFieldSubType | undefined; - format: { - id: any; - params: any; - } | undefined; - }; + }): FieldSpec; // (undocumented) get type(): string; // (undocumented) @@ -1323,7 +1299,6 @@ export const indexPatterns: { formatHitProvider: typeof formatHitProvider; }; -// Warning: (ae-forgotten-export) The symbol "IndexPatternsService" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "IndexPatternsContract" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1356,6 +1331,67 @@ export class IndexPatternSelect extends Component { UNSAFE_componentWillReceiveProps(nextProps: IndexPatternSelectProps): void; } +// Warning: (ae-missing-release-tag) "IndexPatternSpec" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface IndexPatternSpec { + // (undocumented) + fields?: IndexPatternFieldMap; + // (undocumented) + id?: string; + // (undocumented) + intervalName?: string; + // (undocumented) + sourceFilters?: SourceFilter[]; + // (undocumented) + timeFieldName?: string; + // (undocumented) + title?: string; + // (undocumented) + type?: string; + // (undocumented) + typeMeta?: IndexPatternTypeMeta; + // (undocumented) + version?: string; +} + +// Warning: (ae-missing-release-tag) "IndexPatternsService" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class IndexPatternsService { + // Warning: (ae-forgotten-export) The symbol "IndexPatternsServiceDeps" needs to be exported by the entry point index.d.ts + constructor({ uiSettings, savedObjectsClient, apiClient, fieldFormats, onNotification, onError, onRedirectNoIndexPattern, }: IndexPatternsServiceDeps); + clearCache: (id?: string | undefined) => void; + create(spec: IndexPatternSpec, skipFetchFields?: boolean): Promise; + createAndSave(spec: IndexPatternSpec, override?: boolean, skipFetchFields?: boolean): Promise; + createSavedObject(indexPattern: IndexPattern, override?: boolean): Promise; + delete(indexPatternId: string): Promise<{}>; + // Warning: (ae-forgotten-export) The symbol "EnsureDefaultIndexPattern" needs to be exported by the entry point index.d.ts + // + // (undocumented) + ensureDefaultIndexPattern: EnsureDefaultIndexPattern; + fieldArrayToMap: (fields: FieldSpec[]) => Record; + get: (id: string) => Promise; + // Warning: (ae-forgotten-export) The symbol "IndexPatternSavedObjectAttrs" needs to be exported by the entry point index.d.ts + // + // (undocumented) + getCache: () => Promise[] | null | undefined>; + getDefault: () => Promise; + getFieldsForIndexPattern: (indexPattern: IndexPattern | IndexPatternSpec, options?: GetFieldsOptions) => Promise; + // Warning: (ae-forgotten-export) The symbol "GetFieldsOptions" needs to be exported by the entry point index.d.ts + getFieldsForWildcard: (options?: GetFieldsOptions) => Promise; + getIds: (refresh?: boolean) => Promise; + getIdsWithTitle: (refresh?: boolean) => Promise>; + getTitles: (refresh?: boolean) => Promise; + refreshFields: (indexPattern: IndexPattern) => Promise; + savedObjectToSpec: (savedObject: SavedObject) => IndexPatternSpec; + setDefault: (id: string, force?: boolean) => Promise; + updateSavedObject(indexPattern: IndexPattern, saveAttempts?: number): Promise; +} + // Warning: (ae-missing-release-tag) "TypeMeta" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1381,6 +1417,11 @@ export type InputTimeRange = TimeRange | { to: Moment; }; +// Warning: (ae-missing-release-tag) "isCompleteResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const isCompleteResponse: (response?: IEsSearchResponse | undefined) => boolean | undefined; + // Warning: (ae-missing-release-tag) "ISearch" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1433,6 +1474,11 @@ export interface ISearchStartSearchSource { createEmpty: () => ISearchSource; } +// Warning: (ae-missing-release-tag) "isErrorResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const isErrorResponse: (response?: IEsSearchResponse | undefined) => boolean | undefined; + // Warning: (ae-missing-release-tag) "isFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1443,6 +1489,11 @@ export const isFilter: (x: unknown) => x is Filter; // @public (undocumented) export const isFilters: (x: unknown) => x is Filter[]; +// Warning: (ae-missing-release-tag) "isPartialResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const isPartialResponse: (response?: IEsSearchResponse | undefined) => boolean | undefined; + // Warning: (ae-missing-release-tag) "isQuery" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1676,11 +1727,54 @@ export interface QueryStateChange extends QueryStateChangePartial { globalFilters?: boolean; } -// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "QueryStringInput" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const QueryStringInput: React.FC>; +export const QueryStringInput: React.FC; + +// Warning: (ae-missing-release-tag) "QueryStringInputProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface QueryStringInputProps { + // (undocumented) + bubbleSubmitEvent?: boolean; + // (undocumented) + className?: string; + // (undocumented) + dataTestSubj?: string; + // (undocumented) + disableAutoFocus?: boolean; + // (undocumented) + indexPatterns: Array; + // (undocumented) + isInvalid?: boolean; + // (undocumented) + languageSwitcherPopoverAnchorPosition?: PopoverAnchorPosition; + // (undocumented) + onBlur?: () => void; + // (undocumented) + onChange?: (query: Query) => void; + // (undocumented) + onChangeQueryInputFocus?: (isFocused: boolean) => void; + // (undocumented) + onSubmit?: (query: Query) => void; + // Warning: (ae-forgotten-export) The symbol "PersistedLog" needs to be exported by the entry point index.d.ts + // + // (undocumented) + persistedLog?: PersistedLog; + // (undocumented) + placeholder?: string; + // (undocumented) + prepend?: any; + // (undocumented) + query: Query; + // (undocumented) + screenTitle?: string; + // Warning: (ae-forgotten-export) The symbol "SuggestionsListSize" needs to be exported by the entry point index.d.ts + // + // (undocumented) + size?: SuggestionsListSize; +} // @public (undocumented) export type QuerySuggestion = QuerySuggestionBasic | QuerySuggestionField; @@ -2189,6 +2283,7 @@ export const UI_SETTINGS: { // src/plugins/data/common/es_query/filters/meta_filter.ts:54:3 - (ae-forgotten-export) The symbol "FilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/phrase_filter.ts:33:3 - (ae-forgotten-export) The symbol "PhraseFilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/phrases_filter.ts:31:3 - (ae-forgotten-export) The symbol "PhrasesFilterMeta" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:70:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts // src/plugins/data/common/search/aggs/types.ts:98:51 - (ae-forgotten-export) The symbol "AggTypesRegistryStart" needs to be exported by the entry point index.d.ts // src/plugins/data/public/field_formats/field_formats_service.ts:67:3 - (ae-forgotten-export) The symbol "FormatFactory" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "FilterLabel" needs to be exported by the entry point index.d.ts @@ -2217,27 +2312,27 @@ export const UI_SETTINGS: { // src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:380:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:380:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:380:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:380:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:382:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:383:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:392:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:393:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:394:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:395:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:403:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:404:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:407:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:385:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:385:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:385:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:385:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:387:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:388:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:397:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:398:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:404:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:405:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:408:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:409:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:45:5 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/data/public/ui/index.ts b/src/plugins/data/public/ui/index.ts index 35b1bc50ddb1e..299b9d2681578 100644 --- a/src/plugins/data/public/ui/index.ts +++ b/src/plugins/data/public/ui/index.ts @@ -20,7 +20,7 @@ export { SuggestionsComponent } from './typeahead'; export { IndexPatternSelect } from './index_pattern_select'; export { FilterBar } from './filter_bar'; -export { QueryStringInput } from './query_string_input/query_string_input'; +export { QueryStringInput, QueryStringInputProps } from './query_string_input/query_string_input'; export { SearchBar, SearchBarProps, StatefulSearchBarProps } from './search_bar'; // @internal diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx index 8e1151b387fee..0986ad0668c24 100644 --- a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx @@ -46,8 +46,7 @@ import { PersistedLog, getQueryLog, matchPairs, toUser, fromUser } from '../../q import { SuggestionsListSize } from '../typeahead/suggestions_component'; import { SuggestionsComponent } from '..'; -interface Props { - kibana: KibanaReactContextValue; +export interface QueryStringInputProps { indexPatterns: Array; query: Query; disableAutoFocus?: boolean; @@ -67,6 +66,10 @@ interface Props { isInvalid?: boolean; } +interface Props extends QueryStringInputProps { + kibana: KibanaReactContextValue; +} + interface State { isSuggestionsVisible: boolean; index: number | null; @@ -687,4 +690,4 @@ export class QueryStringInputUI extends Component { } } -export const QueryStringInput = withKibana(QueryStringInputUI); +export const QueryStringInput: React.FC = withKibana(QueryStringInputUI); diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index 03baff4910309..43080cc5a5989 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -133,16 +133,17 @@ export { IndexPatternsFetcher, FieldDescriptor as IndexPatternFieldDescriptor, shouldReadFieldFromDocValues, // used only in logstash_fields fixture + FieldDescriptor, } from './index_patterns'; export { - IIndexPattern, IFieldType, IFieldSubType, ES_FIELD_TYPES, KBN_FIELD_TYPES, IndexPatternAttributes, UI_SETTINGS, + IndexPattern, } from '../common'; /** @@ -289,3 +290,5 @@ export const config: PluginConfigDescriptor = { }, schema: configSchema, }; + +export type { IndexPatternsService } from './index_patterns'; diff --git a/src/plugins/data/server/index_patterns/utils.ts b/src/plugins/data/server/index_patterns/utils.ts index e841097fe49c2..1e7a85599612c 100644 --- a/src/plugins/data/server/index_patterns/utils.ts +++ b/src/plugins/data/server/index_patterns/utils.ts @@ -18,11 +18,11 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; -import { IIndexPattern, IFieldType } from '../../common'; +import { IFieldType, IndexPatternAttributes, SavedObject } from '../../common'; export const getFieldByName = ( fieldName: string, - indexPattern: IIndexPattern + indexPattern: SavedObject ): IFieldType | undefined => { const fields: IFieldType[] = indexPattern && JSON.parse(indexPattern.attributes.fields); const field = fields && fields.find((f) => f.name === fieldName); @@ -33,8 +33,8 @@ export const getFieldByName = ( export const findIndexPatternById = async ( savedObjectsClient: SavedObjectsClientContract, index: string -): Promise => { - const savedObjectsResponse = await savedObjectsClient.find({ +): Promise | undefined> => { + const savedObjectsResponse = await savedObjectsClient.find({ type: 'index-pattern', fields: ['fields'], search: `"${index}"`, @@ -42,6 +42,6 @@ export const findIndexPatternById = async ( }); if (savedObjectsResponse.total > 0) { - return (savedObjectsResponse.saved_objects[0] as unknown) as IIndexPattern; + return savedObjectsResponse.saved_objects[0]; } }; diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 2024e9e7f2974..6d4112543ce0e 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -36,6 +36,7 @@ import { ConfigDeprecationProvider } from '@kbn/config'; import { CoreSetup } from 'src/core/server'; import { CoreSetup as CoreSetup_2 } from 'kibana/server'; import { CoreStart } from 'src/core/server'; +import { CoreStart as CoreStart_2 } from 'kibana/server'; import { CountParams } from 'elasticsearch'; import { CreateDocumentParams } from 'elasticsearch'; import { DeleteDocumentByQueryParams } from 'elasticsearch'; @@ -126,6 +127,7 @@ import { PackageInfo } from '@kbn/config'; import { PathConfigType } from '@kbn/utils'; import { PingParams } from 'elasticsearch'; import { Plugin as Plugin_2 } from 'src/core/server'; +import { Plugin as Plugin_3 } from 'kibana/server'; import { PluginInitializerContext as PluginInitializerContext_2 } from 'src/core/server'; import { PutScriptParams } from 'elasticsearch'; import { PutTemplateParams } from 'elasticsearch'; @@ -396,6 +398,32 @@ export interface EsQueryConfig { queryStringOptions: Record; } +// Warning: (ae-missing-release-tag) "FieldDescriptor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +interface FieldDescriptor { + // (undocumented) + aggregatable: boolean; + // (undocumented) + esTypes: string[]; + // (undocumented) + name: string; + // (undocumented) + readFromDocValues: boolean; + // (undocumented) + searchable: boolean; + // Warning: (ae-forgotten-export) The symbol "FieldSubType" needs to be exported by the entry point index.d.ts + // + // (undocumented) + subType?: FieldSubType; + // (undocumented) + type: string; +} + +export { FieldDescriptor } + +export { FieldDescriptor as IndexPatternFieldDescriptor } + // Warning: (ae-missing-release-tag) "FieldFormatConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -467,6 +495,7 @@ export function getShardTimeout(config: SharedGlobalConfig): { timeout?: undefined; }; +// Warning: (ae-forgotten-export) The symbol "IIndexPattern" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "getTime" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -590,40 +619,129 @@ export interface IFieldType { visualizable?: boolean; } -// Warning: (ae-missing-release-tag) "IIndexPattern" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-forgotten-export) The symbol "MetricAggType" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "IMetricAggType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface IIndexPattern { +export type IMetricAggType = MetricAggType; + +// Warning: (ae-missing-release-tag) "IndexPattern" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class IndexPattern implements IIndexPattern { + // Warning: (ae-forgotten-export) The symbol "IndexPatternDeps" needs to be exported by the entry point index.d.ts + constructor({ spec, savedObjectsClient, fieldFormats, shortDotsEnable, metaFields, }: IndexPatternDeps); + addScriptedField(name: string, script: string, fieldType?: string, lang?: string): Promise; // (undocumented) - [key: string]: any; + fieldFormatMap: Record; + // Warning: (ae-forgotten-export) The symbol "IIndexPatternFieldList" needs to be exported by the entry point index.d.ts + // + // (undocumented) + fields: IIndexPatternFieldList & { + toSpec: () => IndexPatternFieldMap; + }; + // (undocumented) + flattenHit: (hit: Record, deep?: boolean) => Record; + // (undocumented) + formatField: FormatFieldFn; + // (undocumented) + formatHit: { + (hit: Record, type?: string): any; + formatField: FormatFieldFn; + }; // (undocumented) - fieldFormatMap?: Record; + getAggregationRestrictions(): Record> | undefined; + getAsSavedObjectBody(): { + title: string; + timeFieldName: string | undefined; + intervalName: string | undefined; + sourceFilters: string | undefined; + fields: string | undefined; + fieldFormatMap: string | undefined; + type: string | undefined; + typeMeta: string | undefined; + }; + // (undocumented) + getComputedFields(): { + storedFields: string[]; + scriptFields: any; + docvalueFields: { + field: any; + format: string; + }[]; + }; + // (undocumented) + getFieldByName(name: string): IndexPatternField | undefined; + getFormatterForField(field: IndexPatternField | IndexPatternField['spec'] | IFieldType): FieldFormat; + // Warning: (ae-forgotten-export) The symbol "IndexPatternField" needs to be exported by the entry point index.d.ts + // // (undocumented) - fields: IFieldType[]; + getNonScriptedFields(): IndexPatternField[]; + getOriginalSavedObjectBody: () => { + title?: string | undefined; + timeFieldName?: string | undefined; + intervalName?: string | undefined; + fields?: string | undefined; + sourceFilters?: string | undefined; + fieldFormatMap?: string | undefined; + typeMeta?: string | undefined; + type?: string | undefined; + }; + // (undocumented) + getScriptedFields(): IndexPatternField[]; + getSourceFiltering(): { + excludes: any[]; + }; // (undocumented) - getTimeField?(): IFieldType | undefined; + getTimeField(): IndexPatternField | undefined; // (undocumented) id?: string; // (undocumented) - timeFieldName?: string; + intervalName: string | undefined; + // (undocumented) + isTimeBased(): boolean; + // (undocumented) + isTimeBasedWildcard(): boolean; + // (undocumented) + isTimeNanosBased(): boolean; + // (undocumented) + metaFields: string[]; + // (undocumented) + popularizeField(fieldName: string, unit?: number): Promise; + removeScriptedField(fieldName: string): void; + resetOriginalSavedObjectBody: () => void; + // Warning: (ae-forgotten-export) The symbol "SourceFilter" needs to be exported by the entry point index.d.ts + // + // (undocumented) + sourceFilters?: SourceFilter[]; + // (undocumented) + timeFieldName: string | undefined; // (undocumented) title: string; + // Warning: (ae-forgotten-export) The symbol "IndexPatternSpec" needs to be exported by the entry point index.d.ts + // + // (undocumented) + toSpec(): IndexPatternSpec; + // (undocumented) + type: string | undefined; + // Warning: (ae-forgotten-export) The symbol "TypeMeta" needs to be exported by the entry point index.d.ts + // + // (undocumented) + typeMeta?: TypeMeta; // (undocumented) - type?: string; + version: string | undefined; } -// Warning: (ae-forgotten-export) The symbol "MetricAggType" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "IMetricAggType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export type IMetricAggType = MetricAggType; - // Warning: (ae-missing-release-tag) "IndexPatternAttributes" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public @deprecated +// @public (undocumented) export interface IndexPatternAttributes { // (undocumented) fieldFormatMap?: string; @@ -643,28 +761,6 @@ export interface IndexPatternAttributes { typeMeta: string; } -// Warning: (ae-missing-release-tag) "FieldDescriptor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export interface IndexPatternFieldDescriptor { - // (undocumented) - aggregatable: boolean; - // (undocumented) - esTypes: string[]; - // (undocumented) - name: string; - // (undocumented) - readFromDocValues: boolean; - // (undocumented) - searchable: boolean; - // Warning: (ae-forgotten-export) The symbol "FieldSubType" needs to be exported by the entry point index.d.ts - // - // (undocumented) - subType?: FieldSubType; - // (undocumented) - type: string; -} - // Warning: (ae-missing-release-tag) "indexPatterns" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -683,14 +779,29 @@ export class IndexPatternsFetcher { metaFields: string[]; lookBack: number; interval: string; - }): Promise; + }): Promise; getFieldsForWildcard(options: { pattern: string | string[]; metaFields?: string[]; fieldCapsOptions?: { allowNoIndices: boolean; }; - }): Promise; + }): Promise; +} + +// Warning: (ae-forgotten-export) The symbol "IndexPatternsServiceStart" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "IndexPatternsService" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class IndexPatternsService implements Plugin_3 { + // (undocumented) + setup(core: CoreSetup_2): void; + // Warning: (ae-forgotten-export) The symbol "IndexPatternsServiceStartDeps" needs to be exported by the entry point index.d.ts + // + // (undocumented) + start(core: CoreStart_2, { fieldFormats, logger }: IndexPatternsServiceStartDeps): { + indexPatternsServiceFactory: (kibanaRequest: KibanaRequest) => Promise; + }; } // Warning: (ae-missing-release-tag) "ISearchOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -892,7 +1003,7 @@ export class Plugin implements Plugin_2 Promise; }; indexPatterns: { - indexPatternsServiceFactory: (kibanaRequest: import("../../../core/server").KibanaRequest) => Promise; + indexPatternsServiceFactory: (kibanaRequest: import("../../../core/server").KibanaRequest) => Promise; }; }; // (undocumented) @@ -926,8 +1037,6 @@ export interface PluginStart { // // (undocumented) fieldFormats: FieldFormatsStart; - // Warning: (ae-forgotten-export) The symbol "IndexPatternsServiceStart" needs to be exported by the entry point index.d.ts - // // (undocumented) indexPatterns: IndexPatternsServiceStart; // (undocumented) @@ -1113,7 +1222,8 @@ export function usageProvider(core: CoreSetup_2): SearchUsage; // // src/plugins/data/common/es_query/filters/meta_filter.ts:53:3 - (ae-forgotten-export) The symbol "FilterState" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/meta_filter.ts:54:3 - (ae-forgotten-export) The symbol "FilterMeta" needs to be exported by the entry point index.d.ts -// src/plugins/data/common/index_patterns/fields/types.ts:41:25 - (ae-forgotten-export) The symbol "IndexPattern" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:64:45 - (ae-forgotten-export) The symbol "IndexPatternFieldMap" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:70:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:40:23 - (ae-forgotten-export) The symbol "buildCustomFilter" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:40:23 - (ae-forgotten-export) The symbol "buildFilter" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:71:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts @@ -1135,19 +1245,20 @@ export function usageProvider(core: CoreSetup_2): SearchUsage; // src/plugins/data/server/index.ts:101:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:127:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:127:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:225:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:225:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:225:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:225:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:227:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:228:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:237:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:238:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:239:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:243:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:244:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:248:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:251:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:226:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:226:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:226:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:226:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:228:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:229:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:238:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:239:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:240:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:244:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:245:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:249:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:252:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index_patterns/index_patterns_service.ts:50:14 - (ae-forgotten-export) The symbol "IndexPatternsService" needs to be exported by the entry point index.d.ts // src/plugins/data/server/plugin.ts:88:66 - (ae-forgotten-export) The symbol "DataEnhancements" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/discover/public/application/components/discover_legacy.tsx b/src/plugins/discover/public/application/components/discover_legacy.tsx index 1a98843649259..9c3d833d73b23 100644 --- a/src/plugins/discover/public/application/components/discover_legacy.tsx +++ b/src/plugins/discover/public/application/components/discover_legacy.tsx @@ -25,7 +25,7 @@ import { IUiSettingsClient, MountPoint } from 'kibana/public'; import { HitsCounter } from './hits_counter'; import { TimechartHeader } from './timechart_header'; import { DiscoverSidebar } from './sidebar'; -import { getServices, IIndexPattern } from '../../kibana_services'; +import { getServices, IndexPattern } from '../../kibana_services'; // @ts-ignore import { DiscoverNoResults } from '../angular/directives/no_results'; import { DiscoverUninitialized } from '../angular/directives/uninitialized'; @@ -58,7 +58,7 @@ export interface DiscoverLegacyProps { fieldCounts: Record; histogramData: Chart; hits: number; - indexPattern: IIndexPattern; + indexPattern: IndexPattern; minimumVisibleRows: number; onAddFilter: (field: IndexPatternField | string, value: string, type: '+' | '-') => void; onChangeInterval: (interval: string) => void; diff --git a/src/plugins/discover/public/application/components/sidebar/lib/field_calculator.test.ts b/src/plugins/discover/public/application/components/sidebar/lib/field_calculator.test.ts index 8746883a5d968..dad208c815675 100644 --- a/src/plugins/discover/public/application/components/sidebar/lib/field_calculator.test.ts +++ b/src/plugins/discover/public/application/components/sidebar/lib/field_calculator.test.ts @@ -142,7 +142,7 @@ describe('fieldCalculator', function () { let hits: any; beforeEach(function () { - hits = _.each(_.cloneDeep(realHits), indexPattern.flattenHit); + hits = _.each(_.cloneDeep(realHits), (hit) => indexPattern.flattenHit(hit)); }); it('Should return an array of values for _source fields', function () { diff --git a/src/plugins/discover/public/application/components/table/table.test.tsx b/src/plugins/discover/public/application/components/table/table.test.tsx index 07e9e0a129a26..2874e2483275b 100644 --- a/src/plugins/discover/public/application/components/table/table.test.tsx +++ b/src/plugins/discover/public/application/components/table/table.test.tsx @@ -22,7 +22,7 @@ import { findTestSubject } from '@elastic/eui/lib/test'; import { DocViewTable } from './table'; import { indexPatterns, IndexPattern } from '../../../../../data/public'; -const indexPattern = { +const indexPattern = ({ fields: { getAll: () => [ { @@ -60,7 +60,7 @@ const indexPattern = { metaFields: ['_index', '_score'], flattenHit: undefined, formatHit: jest.fn((hit) => hit._source), -} as IndexPattern; +} as unknown) as IndexPattern; indexPattern.fields.getByName = (name: string) => { return indexPattern.fields.getAll().find((field) => field.name === name); diff --git a/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx b/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx index 8c3d7ab9c30d0..ba24913c6d1c0 100644 --- a/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx +++ b/src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx @@ -18,7 +18,7 @@ */ import { EditPanelAction } from './edit_panel_action'; -import { Embeddable, EmbeddableInput } from '../embeddables'; +import { Embeddable, EmbeddableInput, SavedObjectEmbeddableInput } from '../embeddables'; import { ViewMode } from '../types'; import { ContactCardEmbeddable } from '../test_samples'; import { embeddablePluginMock } from '../../mocks'; @@ -53,20 +53,50 @@ test('is compatible when edit url is available, in edit mode and editable', asyn ).toBe(true); }); -test('redirects to app using state transfer', async () => { +test('redirects to app using state transfer with by value mode', async () => { applicationMock.currentAppId$ = of('superCoolCurrentApp'); const action = new EditPanelAction(getFactory, applicationMock, stateTransferMock); - const input = { id: '123', viewMode: ViewMode.EDIT }; - const embeddable = new EditableEmbeddable(input, true); + const embeddable = new EditableEmbeddable( + ({ + id: '123', + viewMode: ViewMode.EDIT, + coolInput1: 1, + coolInput2: 2, + } as unknown) as EmbeddableInput, + true + ); + embeddable.getOutput = jest.fn(() => ({ editApp: 'ultraVisualize', editPath: '/123' })); + await action.execute({ embeddable }); + expect(stateTransferMock.navigateToEditor).toHaveBeenCalledWith('ultraVisualize', { + path: '/123', + state: { + originatingApp: 'superCoolCurrentApp', + embeddableId: '123', + valueInput: { + id: '123', + viewMode: ViewMode.EDIT, + coolInput1: 1, + coolInput2: 2, + }, + }, + }); +}); + +test('redirects to app using state transfer without by value mode', async () => { + applicationMock.currentAppId$ = of('superCoolCurrentApp'); + const action = new EditPanelAction(getFactory, applicationMock, stateTransferMock); + const embeddable = new EditableEmbeddable( + { id: '123', viewMode: ViewMode.EDIT, savedObjectId: '1234' } as SavedObjectEmbeddableInput, + true + ); embeddable.getOutput = jest.fn(() => ({ editApp: 'ultraVisualize', editPath: '/123' })); await action.execute({ embeddable }); expect(stateTransferMock.navigateToEditor).toHaveBeenCalledWith('ultraVisualize', { path: '/123', state: { originatingApp: 'superCoolCurrentApp', - byValueMode: true, embeddableId: '123', - valueInput: input, + valueInput: undefined, }, }); }); diff --git a/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts b/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts index 8d12ddd0299e7..cbc28713197ba 100644 --- a/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts +++ b/src/plugins/embeddable/public/lib/actions/edit_panel_action.ts @@ -29,6 +29,8 @@ import { EmbeddableEditorState, EmbeddableStateTransfer, SavedObjectEmbeddableInput, + EmbeddableInput, + Container, } from '../..'; export const ACTION_EDIT_PANEL = 'editPanel'; @@ -118,8 +120,7 @@ export class EditPanelAction implements Action { const byValueMode = !(embeddable.getInput() as SavedObjectEmbeddableInput).savedObjectId; const state: EmbeddableEditorState = { originatingApp: this.currentAppId, - byValueMode, - valueInput: byValueMode ? embeddable.getInput() : undefined, + valueInput: byValueMode ? this.getExplicitInput({ embeddable }) : undefined, embeddableId: embeddable.id, }; return { app, path, state }; @@ -132,4 +133,11 @@ export class EditPanelAction implements Action { const editUrl = embeddable ? embeddable.getOutput().editUrl : undefined; return editUrl ? editUrl : ''; } + + private getExplicitInput({ embeddable }: ActionContext): EmbeddableInput { + return ( + (embeddable.getRoot() as Container)?.getInput()?.panels?.[embeddable.id]?.explicitInput ?? + embeddable.getInput() + ); + } } diff --git a/src/plugins/embeddable/public/lib/containers/container.ts b/src/plugins/embeddable/public/lib/containers/container.ts index 38975cc220bc2..9f701f021162a 100644 --- a/src/plugins/embeddable/public/lib/containers/container.ts +++ b/src/plugins/embeddable/public/lib/containers/container.ts @@ -199,8 +199,8 @@ export abstract class Container< return { type: factory.type, explicitInput: { - id: embeddableId, ...explicitInput, + id: embeddableId, } as TEmbeddableInput, }; } diff --git a/src/plugins/embeddable/public/lib/embeddables/error_embeddable.test.tsx b/src/plugins/embeddable/public/lib/embeddables/error_embeddable.test.tsx index 715827a72c61b..17a2ac3b2a32b 100644 --- a/src/plugins/embeddable/public/lib/embeddables/error_embeddable.test.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/error_embeddable.test.tsx @@ -17,43 +17,36 @@ * under the License. */ import React from 'react'; +import { wait } from '@testing-library/dom'; +import { cleanup, render } from '@testing-library/react/pure'; import { ErrorEmbeddable } from './error_embeddable'; import { EmbeddableRoot } from './embeddable_root'; -import { mount } from 'enzyme'; + +afterEach(cleanup); test('ErrorEmbeddable renders an embeddable', async () => { const embeddable = new ErrorEmbeddable('some error occurred', { id: '123', title: 'Error' }); - const component = mount(); - expect( - component.getDOMNode().querySelectorAll('[data-test-subj="embeddableStackError"]').length - ).toBe(1); - expect( - component.getDOMNode().querySelectorAll('[data-test-subj="errorMessageMarkdown"]').length - ).toBe(1); - expect( - component - .getDOMNode() - .querySelectorAll('[data-test-subj="errorMessageMarkdown"]')[0] - .innerHTML.includes('some error occurred') - ).toBe(true); + const { getByTestId, getByText } = render(); + + expect(getByTestId('embeddableStackError')).toBeVisible(); + await wait(() => getByTestId('errorMessageMarkdown')); // wait for lazy markdown component + expect(getByText(/some error occurred/i)).toBeVisible(); }); test('ErrorEmbeddable renders an embeddable with markdown message', async () => { const error = '[some link](http://localhost:5601/takeMeThere)'; const embeddable = new ErrorEmbeddable(error, { id: '123', title: 'Error' }); - const component = mount(); - expect( - component.getDOMNode().querySelectorAll('[data-test-subj="embeddableStackError"]').length - ).toBe(1); - expect( - component.getDOMNode().querySelectorAll('[data-test-subj="errorMessageMarkdown"]').length - ).toBe(1); - expect( - component - .getDOMNode() - .querySelectorAll('[data-test-subj="errorMessageMarkdown"]')[0] - .innerHTML.includes( - 'some link' - ) - ).toBe(true); + const { getByTestId, getByText } = render(); + + expect(getByTestId('embeddableStackError')).toBeVisible(); + await wait(() => getByTestId('errorMessageMarkdown')); // wait for lazy markdown component + expect(getByText(/some link/i)).toMatchInlineSnapshot(` + + some link + + `); }); diff --git a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts index ef79b18acd4f5..4155cb4d3b60c 100644 --- a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts +++ b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts @@ -85,10 +85,10 @@ describe('embeddable state transfer', () => { it('can send an outgoing embeddable package state', async () => { await stateTransfer.navigateToWithEmbeddablePackage(destinationApp, { - state: { type: 'coolestType', id: '150' }, + state: { type: 'coolestType', input: { savedObjectId: '150' } }, }); expect(application.navigateToApp).toHaveBeenCalledWith('superUltraVisualize', { - state: { type: 'coolestType', id: '150' }, + state: { type: 'coolestType', input: { savedObjectId: '150' } }, }); }); @@ -96,12 +96,16 @@ describe('embeddable state transfer', () => { const historyMock = mockHistoryState({ kibanaIsNowForSports: 'extremeSportsKibana' }); stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, historyMock); await stateTransfer.navigateToWithEmbeddablePackage(destinationApp, { - state: { type: 'coolestType', id: '150' }, + state: { type: 'coolestType', input: { savedObjectId: '150' } }, appendToExistingState: true, }); expect(application.navigateToApp).toHaveBeenCalledWith('superUltraVisualize', { path: undefined, - state: { kibanaIsNowForSports: 'extremeSportsKibana', type: 'coolestType', id: '150' }, + state: { + kibanaIsNowForSports: 'extremeSportsKibana', + type: 'coolestType', + input: { savedObjectId: '150' }, + }, }); }); @@ -120,10 +124,13 @@ describe('embeddable state transfer', () => { }); it('can fetch an incoming embeddable package state', async () => { - const historyMock = mockHistoryState({ type: 'skisEmbeddable', id: '123' }); + const historyMock = mockHistoryState({ + type: 'skisEmbeddable', + input: { savedObjectId: '123' }, + }); stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, historyMock); const fetchedState = stateTransfer.getIncomingEmbeddablePackage(); - expect(fetchedState).toEqual({ type: 'skisEmbeddable', id: '123' }); + expect(fetchedState).toEqual({ type: 'skisEmbeddable', input: { savedObjectId: '123' } }); }); it('returns undefined when embeddable package is not in the right shape', async () => { @@ -136,12 +143,12 @@ describe('embeddable state transfer', () => { it('removes all keys in the keysToRemoveAfterFetch array', async () => { const historyMock = mockHistoryState({ type: 'skisEmbeddable', - id: '123', + input: { savedObjectId: '123' }, test1: 'test1', test2: 'test2', }); stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, historyMock); - stateTransfer.getIncomingEmbeddablePackage({ keysToRemoveAfterFetch: ['type', 'id'] }); + stateTransfer.getIncomingEmbeddablePackage({ keysToRemoveAfterFetch: ['type', 'input'] }); expect(historyMock.replace).toHaveBeenCalledWith( expect.objectContaining({ state: { test1: 'test1', test2: 'test2' } }) ); @@ -150,7 +157,7 @@ describe('embeddable state transfer', () => { it('leaves state as is when no keysToRemove are supplied', async () => { const historyMock = mockHistoryState({ type: 'skisEmbeddable', - id: '123', + input: { savedObjectId: '123' }, test1: 'test1', test2: 'test2', }); @@ -158,7 +165,7 @@ describe('embeddable state transfer', () => { stateTransfer.getIncomingEmbeddablePackage(); expect(historyMock.location.state).toEqual({ type: 'skisEmbeddable', - id: '123', + input: { savedObjectId: '123' }, test1: 'test1', test2: 'test2', }); diff --git a/src/plugins/embeddable/public/lib/state_transfer/types.ts b/src/plugins/embeddable/public/lib/state_transfer/types.ts index 3f3456d914728..d8b4f4801bba3 100644 --- a/src/plugins/embeddable/public/lib/state_transfer/types.ts +++ b/src/plugins/embeddable/public/lib/state_transfer/types.ts @@ -17,17 +17,17 @@ * under the License. */ -import { EmbeddableInput } from '..'; +import { Optional } from '@kbn/utility-types'; +import { EmbeddableInput, SavedObjectEmbeddableInput } from '..'; /** - * Represents a state package that contains the last active app id. + * A state package that contains information an editor will need to create or edit an embeddable then redirect back. * @public */ export interface EmbeddableEditorState { originatingApp: string; - byValueMode?: boolean; - valueInput?: EmbeddableInput; embeddableId?: string; + valueInput?: EmbeddableInput; } export function isEmbeddableEditorState(state: unknown): state is EmbeddableEditorState { @@ -35,32 +35,18 @@ export function isEmbeddableEditorState(state: unknown): state is EmbeddableEdit } /** - * Represents a state package that contains all fields necessary to create an embeddable by reference in a container. + * A state package that contains all fields necessary to create or update an embeddable by reference or by value in a container. * @public */ -export interface EmbeddablePackageByReferenceState { +export interface EmbeddablePackageState { type: string; - id: string; -} - -/** - * Represents a state package that contains all fields necessary to create an embeddable by value in a container. - * @public - */ -export interface EmbeddablePackageByValueState { - type: string; - input: EmbeddableInput; + input: Optional | Optional; embeddableId?: string; } -export type EmbeddablePackageState = - | EmbeddablePackageByReferenceState - | EmbeddablePackageByValueState; - export function isEmbeddablePackageState(state: unknown): state is EmbeddablePackageState { return ( - (ensureFieldOfTypeExists('type', state, 'string') && - ensureFieldOfTypeExists('id', state, 'string')) || + ensureFieldOfTypeExists('type', state, 'string') && ensureFieldOfTypeExists('input', state, 'object') ); } diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/__snapshots__/step_time_field.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/__snapshots__/step_time_field.test.tsx.snap index 6cc92d20cfdcc..544e3ba983122 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/__snapshots__/step_time_field.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/__snapshots__/step_time_field.test.tsx.snap @@ -27,7 +27,7 @@ exports[`StepTimeField should render "Custom index pattern ID already exists" wh /> ({ - fieldsFetcher: { - fetchForWildcard: jest.fn().mockReturnValue(Promise.resolve(fields)), - }, - }), + create: () => ({}), + getFieldsForWildcard: jest.fn().mockReturnValue(Promise.resolve(fields)), } as any; describe('StepTimeField', () => { diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx index 5d33a08557fed..cacabb6d7623b 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx @@ -108,12 +108,12 @@ export class StepTimeField extends Component { }); test('invokes the provided services when creating an index pattern', async () => { - const create = jest.fn().mockImplementation(() => 'id'); + const newIndexPatternAndSave = jest.fn().mockImplementation(async () => { + return indexPattern; + }); const clear = jest.fn(); mockContext.data.indexPatterns.clearCache = clear; const indexPattern = ({ @@ -151,11 +153,10 @@ describe('CreateIndexPatternWizard', () => { title: 'my-fake-index-pattern', timeFieldName: 'timestamp', fields: [], - create, + _fetchFields: jest.fn(), } as unknown) as IndexPattern; - mockContext.data.indexPatterns.make = async () => { - return indexPattern; - }; + mockContext.data.indexPatterns.createAndSave = newIndexPatternAndSave; + mockContext.data.indexPatterns.setDefault = jest.fn(); const component = createComponentWithContext( CreateIndexPatternWizard, @@ -165,9 +166,8 @@ describe('CreateIndexPatternWizard', () => { component.setState({ indexPattern: 'foo' }); await (component.instance() as CreateIndexPatternWizard).createIndexPattern(undefined, 'id'); - expect(mockContext.uiSettings.get).toBeCalled(); - expect(create).toBeCalled(); - expect(clear).toBeCalledWith('id'); - expect(routeComponentPropsMock.history.push).toBeCalledWith(`/patterns/id`); + expect(newIndexPatternAndSave).toBeCalled(); + expect(clear).toBeCalledWith('1'); + expect(routeComponentPropsMock.history.push).toBeCalledWith(`/patterns/1`); }); }); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx index a789ebbfadbce..aa97c21d766b9 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx @@ -40,6 +40,7 @@ import { ensureMinimumTime, getIndices } from './lib'; import { IndexPatternCreationConfig } from '../..'; import { IndexPatternManagmentContextValue } from '../../types'; import { MatchedItem } from './types'; +import { DuplicateIndexPatternError, IndexPattern } from '../../../../data/public'; interface CreateIndexPatternWizardState { step: number; @@ -156,50 +157,50 @@ export class CreateIndexPatternWizard extends Component< }; createIndexPattern = async (timeFieldName: string | undefined, indexPatternId: string) => { + let emptyPattern: IndexPattern; const { history } = this.props; const { indexPattern } = this.state; - const emptyPattern = await this.context.services.data.indexPatterns.make(); - - Object.assign(emptyPattern, { - id: indexPatternId, - title: indexPattern, - timeFieldName, - ...this.state.indexPatternCreationType.getIndexPatternMappings(), - }); - - const createdId = await emptyPattern.create(); - if (!createdId) { - const confirmMessage = i18n.translate( - 'indexPatternManagement.indexPattern.titleExistsLabel', - { - values: { title: emptyPattern.title }, - defaultMessage: "An index pattern with the title '{title}' already exists.", - } - ); - - const isConfirmed = await this.context.services.overlays.openConfirm(confirmMessage, { - confirmButtonText: i18n.translate( - 'indexPatternManagement.indexPattern.goToPatternButtonLabel', + try { + emptyPattern = await this.context.services.data.indexPatterns.createAndSave({ + id: indexPatternId, + title: indexPattern, + timeFieldName, + ...this.state.indexPatternCreationType.getIndexPatternMappings(), + }); + } catch (err) { + if (err instanceof DuplicateIndexPatternError) { + const confirmMessage = i18n.translate( + 'indexPatternManagement.indexPattern.titleExistsLabel', { - defaultMessage: 'Go to existing pattern', + values: { title: emptyPattern!.title }, + defaultMessage: "An index pattern with the title '{title}' already exists.", } - ), - }); + ); + + const isConfirmed = await this.context.services.overlays.openConfirm(confirmMessage, { + confirmButtonText: i18n.translate( + 'indexPatternManagement.indexPattern.goToPatternButtonLabel', + { + defaultMessage: 'Go to existing pattern', + } + ), + }); - if (isConfirmed) { - return history.push(`/patterns/${indexPatternId}`); + if (isConfirmed) { + return history.push(`/patterns/${indexPatternId}`); + } else { + return; + } } else { - return; + throw err; } } - if (!this.context.services.uiSettings.get('defaultIndex')) { - await this.context.services.uiSettings.set('defaultIndex', createdId); - } + await this.context.services.data.indexPatterns.setDefault(emptyPattern.id as string); - this.context.services.data.indexPatterns.clearCache(createdId); - history.push(`/patterns/${createdId}`); + this.context.services.data.indexPatterns.clearCache(emptyPattern.id as string); + history.push(`/patterns/${emptyPattern.id}`); }; goToTimeFieldStep = (indexPattern: string, selectedTimeField?: string) => { diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx index 13be9ca6c9c25..08edf42df60d8 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/create_edit_field/create_edit_field.tsx @@ -96,7 +96,7 @@ export const CreateEditField = withRouter( indexPattern={indexPattern} spec={spec} services={{ - saveIndexPattern: data.indexPatterns.save.bind(data.indexPatterns), + saveIndexPattern: data.indexPatterns.updateSavedObject.bind(data.indexPatterns), redirectAway, }} /> diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx index d09836019b0bc..67a20c428040f 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/edit_index_pattern.tsx @@ -121,7 +121,8 @@ export const EditIndexPattern = withRouter( const refreshFields = () => { overlays.openConfirm(confirmMessage, confirmModalOptionsRefresh).then(async (isConfirmed) => { if (isConfirmed) { - await indexPattern.refreshFields(); + await data.indexPatterns.refreshFields(indexPattern); + await data.indexPatterns.updateSavedObject(indexPattern); setFields(indexPattern.getNonScriptedFields()); } }); @@ -236,7 +237,7 @@ export const EditIndexPattern = withRouter( ({ @@ -43,7 +43,7 @@ const helpers = { const indexPattern = ({ getNonScriptedFields: () => fields, -} as unknown) as IIndexPattern; +} as unknown) as IndexPattern; const mockFieldToIndexPatternField = (spec: Record) => { return new IndexPatternField( diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx index 23977aac7fa7a..7be420e2af50d 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx @@ -19,23 +19,19 @@ import React, { Component } from 'react'; import { createSelector } from 'reselect'; -import { - IndexPatternField, - IIndexPattern, - IFieldType, -} from '../../../../../../plugins/data/public'; +import { IndexPatternField, IndexPattern, IFieldType } from '../../../../../../plugins/data/public'; import { Table } from './components/table'; import { getFieldFormat } from './lib'; import { IndexedFieldItem } from './types'; interface IndexedFieldsTableProps { fields: IndexPatternField[]; - indexPattern: IIndexPattern; + indexPattern: IndexPattern; fieldFilter?: string; indexedFieldTypeFilter?: string; helpers: { redirectToRoute: (obj: any) => void; - getFieldInfo: (indexPattern: IIndexPattern, field: IFieldType) => string[]; + getFieldInfo: (indexPattern: IndexPattern, field: IFieldType) => string[]; }; fieldWildcardMatcher: (filters: any[]) => (val: any) => boolean; } diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx index 08cc90faf75fa..c7ea20c700086 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/scripted_fields_table.tsx @@ -39,7 +39,7 @@ interface ScriptedFieldsTableProps { }; onRemoveField?: () => void; painlessDocLink: string; - saveIndexPattern: DataPublicPluginStart['indexPatterns']['save']; + saveIndexPattern: DataPublicPluginStart['indexPatterns']['updateSavedObject']; } interface ScriptedFieldsTableState { diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/table/table.test.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/table/table.test.tsx index e43ee2e55eeca..2d3a61b42c3a4 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/table/table.test.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/table/table.test.tsx @@ -22,13 +22,13 @@ import { shallow, ShallowWrapper } from 'enzyme'; import { Table, TableProps, TableState } from './table'; import { EuiTableFieldDataColumnType, keys } from '@elastic/eui'; -import { IIndexPattern } from 'src/plugins/data/public'; +import { IndexPattern } from 'src/plugins/data/public'; import { SourceFiltersTableFilter } from '../../types'; -const indexPattern = {} as IIndexPattern; +const indexPattern = {} as IndexPattern; const items: SourceFiltersTableFilter[] = [{ value: 'tim*', clientId: '' }]; -const getIndexPatternMock = (mockedFields: any = {}) => ({ ...mockedFields } as IIndexPattern); +const getIndexPatternMock = (mockedFields: any = {}) => ({ ...mockedFields } as IndexPattern); const getTableColumnRender = ( component: ShallowWrapper, diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/table/table.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/table/table.tsx index f73d756f28116..c5b09961f25fc 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/table/table.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/table/table.tsx @@ -30,7 +30,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { IIndexPattern } from 'src/plugins/data/public'; +import { IndexPattern } from 'src/plugins/data/public'; import { SourceFiltersTableFilter } from '../../types'; const filterHeader = i18n.translate( @@ -80,7 +80,7 @@ const cancelAria = i18n.translate( ); export interface TableProps { - indexPattern: IIndexPattern; + indexPattern: IndexPattern; items: SourceFiltersTableFilter[]; deleteFilter: Function; fieldWildcardMatcher: Function; diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.tsx index b00648f124716..cd311db513c09 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/source_filters_table.tsx @@ -30,7 +30,7 @@ export interface SourceFiltersTableProps { filterFilter: string; fieldWildcardMatcher: Function; onAddOrRemoveFilter?: Function; - saveIndexPattern: DataPublicPluginStart['indexPatterns']['save']; + saveIndexPattern: DataPublicPluginStart['indexPatterns']['updateSavedObject']; } export interface SourceFiltersTableState { diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx index 101399ef02b73..5c29dfafd3c07 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx @@ -49,7 +49,7 @@ import { getTabs, getPath, convertToEuiSelectOption } from './utils'; interface TabsProps extends Pick { indexPattern: IndexPattern; fields: IndexPatternField[]; - saveIndexPattern: DataPublicPluginStart['indexPatterns']['save']; + saveIndexPattern: DataPublicPluginStart['indexPatterns']['updateSavedObject']; } const searchAriaLabel = i18n.translate( diff --git a/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx b/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx index 2b484d1d837bf..4fae91e78f8f9 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx +++ b/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx @@ -133,7 +133,7 @@ export interface FieldEdiorProps { spec: IndexPatternField['spec']; services: { redirectAway: () => void; - saveIndexPattern: DataPublicPluginStart['indexPatterns']['save']; + saveIndexPattern: DataPublicPluginStart['indexPatterns']['updateSavedObject']; }; } @@ -825,7 +825,7 @@ export class FieldEditor extends PureComponent { + .catch(() => { if (oldField) { indexPattern.fields.update(oldField); } else { diff --git a/src/plugins/kibana_react/public/code_editor/code_editor.tsx b/src/plugins/kibana_react/public/code_editor/code_editor.tsx index f049085ccff61..85b2c327e8b21 100644 --- a/src/plugins/kibana_react/public/code_editor/code_editor.tsx +++ b/src/plugins/kibana_react/public/code_editor/code_editor.tsx @@ -186,3 +186,7 @@ export class CodeEditor extends React.Component { } }; } + +// React.lazy requires default export +// eslint-disable-next-line import/no-default-export +export default CodeEditor; diff --git a/src/plugins/kibana_react/public/code_editor/index.tsx b/src/plugins/kibana_react/public/code_editor/index.tsx index 0ef83811d96d3..63e3d330a1c52 100644 --- a/src/plugins/kibana_react/public/code_editor/index.tsx +++ b/src/plugins/kibana_react/public/code_editor/index.tsx @@ -17,11 +17,23 @@ * under the License. */ import React from 'react'; +import { EuiDelayRender, EuiLoadingContent } from '@elastic/eui'; import { useUiSetting } from '../ui_settings'; -import { CodeEditor as BaseEditor, Props } from './code_editor'; +import type { Props } from './code_editor'; + +const LazyBaseEditor = React.lazy(() => import('./code_editor')); + +const Fallback = () => ( + + + +); export const CodeEditor: React.FunctionComponent = (props) => { const darkMode = useUiSetting('theme:darkMode'); - - return ; + return ( + }> + + + ); }; diff --git a/src/plugins/kibana_react/public/markdown/index.tsx b/src/plugins/kibana_react/public/markdown/index.tsx index cacf223cf33ed..4d6fd0b742ee4 100644 --- a/src/plugins/kibana_react/public/markdown/index.tsx +++ b/src/plugins/kibana_react/public/markdown/index.tsx @@ -17,5 +17,27 @@ * under the License. */ -export { MarkdownSimple } from './markdown_simple'; -export { Markdown } from './markdown'; +import React from 'react'; +import { EuiLoadingContent, EuiDelayRender } from '@elastic/eui'; +import type { MarkdownSimpleProps } from './markdown_simple'; +import type { MarkdownProps } from './markdown'; + +const Fallback = () => ( + + + +); + +const LazyMarkdownSimple = React.lazy(() => import('./markdown_simple')); +export const MarkdownSimple = (props: MarkdownSimpleProps) => ( + }> + + +); + +const LazyMarkdown = React.lazy(() => import('./markdown')); +export const Markdown = (props: MarkdownProps) => ( + }> + + +); diff --git a/src/plugins/kibana_react/public/markdown/markdown.tsx b/src/plugins/kibana_react/public/markdown/markdown.tsx index 15d1c4931e60b..8bb61bc71862e 100644 --- a/src/plugins/kibana_react/public/markdown/markdown.tsx +++ b/src/plugins/kibana_react/public/markdown/markdown.tsx @@ -84,7 +84,7 @@ export const markdownFactory = memoize( } ); -interface MarkdownProps extends React.HTMLAttributes { +export interface MarkdownProps extends React.HTMLAttributes { className?: string; markdown?: string; openLinksInNewTab?: boolean; @@ -112,3 +112,7 @@ export class Markdown extends PureComponent { ); } } + +// Needed for React.lazy +// eslint-disable-next-line import/no-default-export +export default Markdown; diff --git a/src/plugins/kibana_react/public/markdown/markdown_simple.tsx b/src/plugins/kibana_react/public/markdown/markdown_simple.tsx index a5465fd1c6fc9..71ae4e031abca 100644 --- a/src/plugins/kibana_react/public/markdown/markdown_simple.tsx +++ b/src/plugins/kibana_react/public/markdown/markdown_simple.tsx @@ -24,7 +24,7 @@ const markdownRenderers = { root: Fragment, }; -interface MarkdownSimpleProps { +export interface MarkdownSimpleProps { children: string; } @@ -32,3 +32,7 @@ interface MarkdownSimpleProps { export const MarkdownSimple = ({ children }: MarkdownSimpleProps) => ( {children} ); + +// Needed for React.lazy +// eslint-disable-next-line import/no-default-export +export default MarkdownSimple; diff --git a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx index 2fa1debf51b5c..e0e295723a69d 100644 --- a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx +++ b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx @@ -556,3 +556,6 @@ class TableListView extends React.Component import('react-markdown')); +const ErrorRenderer = (props: { children: string }) => ( + }> + + +); + interface Mapping { [key: string]: string | { app: string; path: string }; } @@ -96,16 +103,7 @@ export function redirectWhenMissing({ defaultMessage: 'Saved object is missing', }), text: (element: HTMLElement) => { - ReactDOM.render( - - {error.message} - , - element - ); + ReactDOM.render({error.message}, element); return () => ReactDOM.unmountComponentAtNode(element); }, }); diff --git a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_string/index.js b/src/plugins/management/common/contants.ts similarity index 94% rename from src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_string/index.js rename to src/plugins/management/common/contants.ts index 8900db15321ae..6ff585510dab1 100644 --- a/src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/exports_string/index.js +++ b/src/plugins/management/common/contants.ts @@ -17,4 +17,4 @@ * under the License. */ -export default 'foo'; +export const MANAGEMENT_APP_ID = 'management'; diff --git a/src/plugins/management/public/index.ts b/src/plugins/management/public/index.ts index f6c23ccf0143f..f3e25b90b73c7 100644 --- a/src/plugins/management/public/index.ts +++ b/src/plugins/management/public/index.ts @@ -32,3 +32,5 @@ export { ManagementStart, DefinedSections, } from './types'; + +export { MANAGEMENT_APP_ID } from '../common/contants'; diff --git a/src/plugins/management/public/plugin.ts b/src/plugins/management/public/plugin.ts index 808578c470ae1..122e73796753c 100644 --- a/src/plugins/management/public/plugin.ts +++ b/src/plugins/management/public/plugin.ts @@ -33,6 +33,7 @@ import { AppNavLinkStatus, } from '../../../core/public'; +import { MANAGEMENT_APP_ID } from '../common/contants'; import { ManagementSectionsService, getSectionsServiceStartPrivate, @@ -72,7 +73,7 @@ export class ManagementPlugin implements Plugin string | undefined; + originatingAppName?: string; + returnToOriginSwitchLabel?: string; documentInfo: SaveModalDocumentInfo; objectType: string; onClose: () => void; @@ -73,11 +75,13 @@ export function SavedObjectSaveModalOrigin(props: OriginSaveModalProps) { setReturnToOriginMode(event.target.checked); }} label={ - + props.returnToOriginSwitchLabel ?? ( + + ) } /> 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 679ea5ffc23ee..eb95c213e680d 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 @@ -24,10 +24,11 @@ import { SavedObject, SavedObjectLoader } from '../../../saved_objects/public'; import { DataPublicPluginStart, IndexPatternsContract, - IIndexPattern, injectSearchSourceReferences, + IndexPatternSpec, } from '../../../data/public'; import { FailedImport } from './process_import_response'; +import { DuplicateIndexPatternError, IndexPattern } from '../../../data/public'; type SavedObjectsRawDoc = Record; @@ -70,11 +71,10 @@ function addJsonFieldToIndexPattern( async function importIndexPattern( doc: SavedObjectsRawDoc, indexPatterns: IndexPatternsContract, - overwriteAll: boolean, + overwriteAll: boolean = false, openConfirm: OverlayStart['openConfirm'] ) { // TODO: consolidate this is the code in create_index_pattern_wizard.js - const emptyPattern = await indexPatterns.make(); const { title, timeFieldName, @@ -84,50 +84,53 @@ async function importIndexPattern( type, typeMeta, } = doc._source; - const importedIndexPattern = { + const indexPatternSpec: IndexPatternSpec = { id: doc._id, title, timeFieldName, - } as IIndexPattern; + }; + let emptyPattern: IndexPattern; if (type) { - importedIndexPattern.type = type; + indexPatternSpec.type = type; } - addJsonFieldToIndexPattern(importedIndexPattern, fields, 'fields', title); - addJsonFieldToIndexPattern(importedIndexPattern, fieldFormatMap, 'fieldFormatMap', title); - addJsonFieldToIndexPattern(importedIndexPattern, sourceFilters, 'sourceFilters', title); - addJsonFieldToIndexPattern(importedIndexPattern, typeMeta, 'typeMeta', title); - Object.assign(emptyPattern, importedIndexPattern); - - let newId = await emptyPattern.create(overwriteAll); - if (!newId) { - // We can override and we want to prompt for confirmation - const isConfirmed = await openConfirm( - i18n.translate('savedObjectsManagement.indexPattern.confirmOverwriteLabel', { - values: { title }, - defaultMessage: "Are you sure you want to overwrite '{title}'?", - }), - { - title: i18n.translate('savedObjectsManagement.indexPattern.confirmOverwriteTitle', { - defaultMessage: 'Overwrite {type}?', - values: { type }, + addJsonFieldToIndexPattern(indexPatternSpec, fields, 'fields', title); + addJsonFieldToIndexPattern(indexPatternSpec, fieldFormatMap, 'fieldFormatMap', title); + addJsonFieldToIndexPattern(indexPatternSpec, sourceFilters, 'sourceFilters', title); + addJsonFieldToIndexPattern(indexPatternSpec, typeMeta, 'typeMeta', title); + try { + emptyPattern = await indexPatterns.createAndSave(indexPatternSpec, overwriteAll, true); + } catch (err) { + if (err instanceof DuplicateIndexPatternError) { + // We can override and we want to prompt for confirmation + const isConfirmed = await openConfirm( + i18n.translate('savedObjectsManagement.indexPattern.confirmOverwriteLabel', { + values: { title }, + defaultMessage: "Are you sure you want to overwrite '{title}'?", }), - confirmButtonText: i18n.translate( - 'savedObjectsManagement.indexPattern.confirmOverwriteButton', - { - defaultMessage: 'Overwrite', - } - ), - } - ); + { + title: i18n.translate('savedObjectsManagement.indexPattern.confirmOverwriteTitle', { + defaultMessage: 'Overwrite {type}?', + values: { type }, + }), + confirmButtonText: i18n.translate( + 'savedObjectsManagement.indexPattern.confirmOverwriteButton', + { + defaultMessage: 'Overwrite', + } + ), + } + ); - if (isConfirmed) { - newId = (await emptyPattern.create(true)) as string; - } else { - return; + if (isConfirmed) { + emptyPattern = await indexPatterns.createAndSave(indexPatternSpec, true, true); + } else { + return; + } } } - indexPatterns.clearCache(newId); - return newId; + + indexPatterns.clearCache(emptyPattern!.id); + return emptyPattern!.id; } async function importDocument(obj: SavedObject, doc: SavedObjectsRawDoc, overwriteAll: boolean) { diff --git a/src/plugins/ui_actions/public/mocks.ts b/src/plugins/ui_actions/public/mocks.ts index 3522ac4941ba0..759430169b613 100644 --- a/src/plugins/ui_actions/public/mocks.ts +++ b/src/plugins/ui_actions/public/mocks.ts @@ -48,6 +48,7 @@ const createStartContract = (): Start => { executeTriggerActions: jest.fn(), fork: jest.fn(), getAction: jest.fn(), + hasAction: jest.fn(), getTrigger: jest.fn(), getTriggerActions: jest.fn((id: TriggerId) => []), getTriggerCompatibleActions: jest.fn(), diff --git a/src/plugins/ui_actions/public/service/ui_actions_service.ts b/src/plugins/ui_actions/public/service/ui_actions_service.ts index 6028177964fb7..ec5f3afa19c94 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_service.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_service.ts @@ -99,6 +99,10 @@ export class UiActionsService { this.actions.delete(actionId); }; + public readonly hasAction = (actionId: string): boolean => { + return this.actions.has(actionId); + }; + public readonly attachAction = (triggerId: T, actionId: string): void => { const trigger = this.triggers.get(triggerId); diff --git a/src/plugins/vis_type_markdown/public/markdown_vis_controller.test.tsx b/src/plugins/vis_type_markdown/public/markdown_vis_controller.test.tsx index ff0cc89a5d9c9..6df205b21d910 100644 --- a/src/plugins/vis_type_markdown/public/markdown_vis_controller.test.tsx +++ b/src/plugins/vis_type_markdown/public/markdown_vis_controller.test.tsx @@ -18,11 +18,14 @@ */ import React from 'react'; -import { render, mount } from 'enzyme'; +import { wait } from '@testing-library/dom'; +import { render, cleanup } from '@testing-library/react/pure'; import { MarkdownVisWrapper } from './markdown_vis_controller'; +afterEach(cleanup); + describe('markdown vis controller', () => { - it('should set html from markdown params', () => { + it('should set html from markdown params', async () => { const vis = { params: { openLinksInNewTab: false, @@ -32,13 +35,22 @@ describe('markdown vis controller', () => { }, }; - const wrapper = render( + const { getByTestId, getByText } = render( ); - expect(wrapper.find('a').text()).toBe('markdown'); + + await wait(() => getByTestId('markdownBody')); + + expect(getByText('markdown')).toMatchInlineSnapshot(` + + markdown + + `); }); - it('should not render the html', () => { + it('should not render the html', async () => { const vis = { params: { openLinksInNewTab: false, @@ -47,13 +59,20 @@ describe('markdown vis controller', () => { }, }; - const wrapper = render( + const { getByTestId, getByText } = render( ); - expect(wrapper.text()).toBe('Testing html\n'); + + await wait(() => getByTestId('markdownBody')); + + expect(getByText(/testing/i)).toMatchInlineSnapshot(` +

+ Testing <a>html</a> +

+ `); }); - it('should update the HTML when render again with changed params', () => { + it('should update the HTML when render again with changed params', async () => { const vis = { params: { openLinksInNewTab: false, @@ -62,13 +81,20 @@ describe('markdown vis controller', () => { }, }; - const wrapper = mount( + const { getByTestId, getByText, rerender } = render( ); - expect(wrapper.text().trim()).toBe('Initial'); + + await wait(() => getByTestId('markdownBody')); + + expect(getByText(/initial/i)).toBeInTheDocument(); + vis.params.markdown = 'Updated'; - wrapper.setProps({ vis }); - expect(wrapper.text().trim()).toBe('Updated'); + rerender( + + ); + + expect(getByText(/Updated/i)).toBeInTheDocument(); }); describe('renderComplete', () => { @@ -86,56 +112,71 @@ describe('markdown vis controller', () => { renderComplete.mockClear(); }); - it('should be called on initial rendering', () => { - mount( + it('should be called on initial rendering', async () => { + const { getByTestId } = render( ); - expect(renderComplete.mock.calls.length).toBe(1); + + await wait(() => getByTestId('markdownBody')); + + expect(renderComplete).toHaveBeenCalledTimes(1); }); - it('should be called on successive render when params change', () => { - mount( + it('should be called on successive render when params change', async () => { + const { getByTestId, rerender } = render( ); - expect(renderComplete.mock.calls.length).toBe(1); + + await wait(() => getByTestId('markdownBody')); + + expect(renderComplete).toHaveBeenCalledTimes(1); + renderComplete.mockClear(); vis.params.markdown = 'changed'; - mount( + + rerender( ); - expect(renderComplete.mock.calls.length).toBe(1); + + expect(renderComplete).toHaveBeenCalledTimes(1); }); - it('should be called on successive render even without data change', () => { - mount( + it('should be called on successive render even without data change', async () => { + const { getByTestId, rerender } = render( ); - expect(renderComplete.mock.calls.length).toBe(1); + + await wait(() => getByTestId('markdownBody')); + + expect(renderComplete).toHaveBeenCalledTimes(1); + renderComplete.mockClear(); - mount( + + rerender( ); - expect(renderComplete.mock.calls.length).toBe(1); + + expect(renderComplete).toHaveBeenCalledTimes(1); }); }); }); diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts index 18ae68ec40fe5..c091d396b4924 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts @@ -243,12 +243,6 @@ export class VisualizeEmbeddable dirty = true; } - // propagate the title to the output embeddable - // but only when the visualization is in edit/Visualize mode - if (!this.parent && this.vis.title !== this.output.title) { - this.updateOutput({ title: this.vis.title }); - } - if (this.vis.description && this.domNode) { this.domNode.setAttribute('data-description', this.vis.description); } diff --git a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx index 1d01900ceffc2..a9bf6bd171f15 100644 --- a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx +++ b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx @@ -174,7 +174,9 @@ class NewVisModal extends React.Component void; originatingApp?: string; outsideVisualizeApp?: boolean; + createByValue?: boolean; } /** diff --git a/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx b/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx index 0423e48bfb41e..12720f3f22e7c 100644 --- a/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx +++ b/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx @@ -21,8 +21,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { TopNavMenuData } from 'src/plugins/navigation/public'; -import uuid from 'uuid'; -import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../visualizations/public'; +import { VISUALIZE_EMBEDDABLE_TYPE, VisualizeInput } from '../../../../visualizations/public'; import { showSaveModal, SavedObjectSaveModalOrigin, @@ -122,7 +121,7 @@ export const getTopNavConfig = ( if (newlyCreated && stateTransfer) { stateTransfer.navigateToWithEmbeddablePackage(originatingApp, { - state: { id, type: VISUALIZE_EMBEDDABLE_TYPE }, + state: { type: VISUALIZE_EMBEDDABLE_TYPE, input: { savedObjectId: id } }, }); } else { application.navigateToApp(originatingApp); @@ -167,15 +166,11 @@ export const getTopNavConfig = ( } const state = { input: { - ...vis.serialize(), - id: embeddableId ? embeddableId : uuid.v4(), - }, + savedVis: vis.serialize(), + } as VisualizeInput, + embeddableId, type: VISUALIZE_EMBEDDABLE_TYPE, - embeddableId: '', }; - if (embeddableId) { - state.embeddableId = embeddableId; - } stateTransfer.navigateToWithEmbeddablePackage(originatingApp, { state }); }; @@ -267,6 +262,7 @@ export const getTopNavConfig = ( } const currentTitle = savedVis.title; savedVis.title = newTitle; + embeddableHandler.updateInput({ title: newTitle }); savedVis.copyOnSave = newCopyOnSave; savedVis.description = newDescription; const saveOptions = { @@ -282,6 +278,7 @@ export const getTopNavConfig = ( } return response; }; + const saveModal = ( { + // FLAKY: https://github.com/elastic/kibana/issues/39842 + describe.skip('inspect', () => { before(async () => { await esArchiver.loadIfNeeded('logstash_functional'); await esArchiver.load('discover'); diff --git a/test/functional/apps/management/_index_pattern_popularity.js b/test/functional/apps/management/_index_pattern_popularity.js index e2fcf50ef2c12..530b8e1111a0c 100644 --- a/test/functional/apps/management/_index_pattern_popularity.js +++ b/test/functional/apps/management/_index_pattern_popularity.js @@ -60,7 +60,7 @@ export default function ({ getService, getPageObjects }) { // check that it is 0 (previous increase was cancelled const popularity = await PageObjects.settings.getPopularity(); log.debug('popularity = ' + popularity); - expect(popularity).to.be(''); + expect(popularity).to.be('0'); }); it('can be saved', async function () { diff --git a/test/plugin_functional/plugins/index_patterns/server/plugin.ts b/test/plugin_functional/plugins/index_patterns/server/plugin.ts index 1c85f226623cb..ddf9acb259983 100644 --- a/test/plugin_functional/plugins/index_patterns/server/plugin.ts +++ b/test/plugin_functional/plugins/index_patterns/server/plugin.ts @@ -78,7 +78,7 @@ export class IndexPatternsTestPlugin const id = (req.params as Record).id; const service = await data.indexPatterns.indexPatternsServiceFactory(req); const ip = await service.get(id); - await service.save(ip); + await service.updateSavedObject(ip); return res.ok(); } ); diff --git a/test/plugin_functional/test_suites/data_plugin/index_patterns.ts b/test/plugin_functional/test_suites/data_plugin/index_patterns.ts index 2db9eb733f805..7e736ea7a066f 100644 --- a/test/plugin_functional/test_suites/data_plugin/index_patterns.ts +++ b/test/plugin_functional/test_suites/data_plugin/index_patterns.ts @@ -46,14 +46,14 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide const body = await ( await supertest.get(`/api/index-patterns-plugin/get/${indexPatternId}`).expect(200) ).body; - expect(body.fields.length > 0).to.equal(true); + expect(typeof body.id).to.equal('string'); }); it('can update index pattern', async () => { - const body = await ( - await supertest.get(`/api/index-patterns-plugin/update/${indexPatternId}`).expect(200) - ).body; - expect(body).to.eql({}); + const resp = await supertest + .get(`/api/index-patterns-plugin/update/${indexPatternId}`) + .expect(200); + expect(resp.body).to.eql({}); }); it('can delete index pattern', async () => { diff --git a/x-pack/.gitignore b/x-pack/.gitignore index d73b6f64f036a..99e33dbb88e92 100644 --- a/x-pack/.gitignore +++ b/x-pack/.gitignore @@ -6,13 +6,9 @@ /test/functional/apps/reporting/reports/session /test/reporting/configs/failure_debug/ /plugins/reporting/.chromium/ -/legacy/plugins/reporting/.chromium/ -/legacy/plugins/reporting/.phantom/ /plugins/reporting/chromium/ /plugins/reporting/.phantom/ /.aws-config.json /.env /.kibana-plugin-helpers.dev.* -!/legacy/plugins/infra/**/target .cache -!/legacy/plugins/security_solution/**/target diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index a700781438706..66ae478b86828 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -9,8 +9,8 @@ "xpack.alerts": "plugins/alerts", "xpack.eventLog": "plugins/event_log", "xpack.alertingBuiltins": "plugins/alerting_builtins", - "xpack.apm": ["legacy/plugins/apm", "plugins/apm"], - "xpack.beatsManagement": ["legacy/plugins/beats_management", "plugins/beats_management"], + "xpack.apm": "plugins/apm", + "xpack.beatsManagement": "plugins/beats_management", "xpack.canvas": "plugins/canvas", "xpack.cloud": "plugins/cloud", "xpack.dashboard": "plugins/dashboard_enhanced", @@ -35,15 +35,15 @@ "xpack.lens": "plugins/lens", "xpack.licenseMgmt": "plugins/license_management", "xpack.licensing": "plugins/licensing", - "xpack.logstash": ["plugins/logstash", "legacy/plugins/logstash"], + "xpack.logstash": ["plugins/logstash"], "xpack.main": "legacy/plugins/xpack_main", - "xpack.maps": ["plugins/maps", "legacy/plugins/maps"], - "xpack.ml": ["plugins/ml", "legacy/plugins/ml"], + "xpack.maps": ["plugins/maps"], + "xpack.ml": ["plugins/ml"], "xpack.monitoring": ["plugins/monitoring"], "xpack.remoteClusters": "plugins/remote_clusters", "xpack.painlessLab": "plugins/painless_lab", "xpack.reporting": ["plugins/reporting"], - "xpack.rollupJobs": ["legacy/plugins/rollup", "plugins/rollup"], + "xpack.rollupJobs": ["plugins/rollup"], "xpack.searchProfiler": "plugins/searchprofiler", "xpack.security": "plugins/security", "xpack.server": "legacy/server", @@ -55,6 +55,7 @@ "xpack.triggersActionsUI": "plugins/triggers_actions_ui", "xpack.upgradeAssistant": "plugins/upgrade_assistant", "xpack.uptime": ["plugins/uptime"], + "xpack.urlDrilldown": "plugins/drilldowns/url_drilldown", "xpack.watcher": "plugins/watcher", "xpack.observability": "plugins/observability" }, diff --git a/x-pack/dev-tools/jest/create_jest_config.js b/x-pack/dev-tools/jest/create_jest_config.js index e6f160ce8c654..eec7b0246d026 100644 --- a/x-pack/dev-tools/jest/create_jest_config.js +++ b/x-pack/dev-tools/jest/create_jest_config.js @@ -8,17 +8,15 @@ export function createJestConfig({ kibanaDirectory, rootDir, xPackKibanaDirector const fileMockPath = `${kibanaDirectory}/src/dev/jest/mocks/file_mock.js`; return { rootDir, - roots: ['/plugins', '/legacy/plugins', '/legacy/server'], + roots: ['/plugins'], moduleFileExtensions: ['js', 'mjs', 'json', 'ts', 'tsx', 'node'], moduleNameMapper: { '@elastic/eui$': `${kibanaDirectory}/node_modules/@elastic/eui/test-env`, '@elastic/eui/lib/(.*)?': `${kibanaDirectory}/node_modules/@elastic/eui/test-env/$1`, '^fixtures/(.*)': `${kibanaDirectory}/src/fixtures/$1`, - 'uiExports/(.*)': fileMockPath, '^src/core/(.*)': `${kibanaDirectory}/src/core/$1`, '^src/legacy/(.*)': `${kibanaDirectory}/src/legacy/$1`, '^src/plugins/(.*)': `${kibanaDirectory}/src/plugins/$1`, - '^legacy/plugins/xpack_main/(.*);': `${xPackKibanaDirectory}/legacy/plugins/xpack_main/public/$1`, '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': fileMockPath, '\\.module.(css|scss)$': `${kibanaDirectory}/src/dev/jest/mocks/css_module_mock.js`, '\\.(css|less|scss)$': `${kibanaDirectory}/src/dev/jest/mocks/style_mock.js`, @@ -30,8 +28,6 @@ export function createJestConfig({ kibanaDirectory, rootDir, xPackKibanaDirector '^(!!)?file-loader!': fileMockPath, }, collectCoverageFrom: [ - 'legacy/plugins/**/*.{js,mjs,jsx,ts,tsx}', - 'legacy/server/**/*.{js,mjs,jsx,ts,tsx}', 'plugins/**/*.{js,mjs,jsx,ts,tsx}', '!**/{__test__,__snapshots__,__examples__,integration_tests,tests}/**', '!**/*.test.{js,mjs,ts,tsx}', diff --git a/x-pack/legacy/common/__tests__/poller.js b/x-pack/legacy/common/__tests__/poller.js deleted file mode 100644 index 24558502a8d02..0000000000000 --- a/x-pack/legacy/common/__tests__/poller.js +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import sinon from 'sinon'; -import { Poller } from '../poller'; - -describe('Poller', () => { - const pollFrequencyInMillis = 20; - let functionToPoll; - let successFunction; - let errorFunction; - let poller; - let clock; - - beforeEach(() => { - clock = sinon.useFakeTimers(); - }); - - afterEach(() => { - clock.restore(); - if (poller) { - poller.stop(); - } - }); - - // Allowing the Poller to poll requires intimate knowledge of the inner workings of the Poller. - // We have to ensure that the Promises internal to the `_poll` method are resolved to queue up - // the next setTimeout before incrementing the clock. The order of this differs slightly when the - // `trailing` is set, hence the different `allowPoll` and `allowDelayPoll` functions. - const queueNextPoll = async () => { - await Promise.resolve(); - await Promise.resolve(); - }; - - const allowPoll = async (interval) => { - await queueNextPoll(); - clock.tick(interval); - }; - - const allowDelayPoll = async (interval) => { - clock.tick(interval); - await queueNextPoll(); - }; - - describe('start()', () => { - beforeEach(() => { - functionToPoll = sinon.spy(() => { - return Promise.resolve(42); - }); - successFunction = sinon.spy(); - errorFunction = sinon.spy(); - poller = new Poller({ - functionToPoll, - successFunction, - errorFunction, - pollFrequencyInMillis, - }); - }); - - describe(`when trailing isn't set`, () => { - it(`polls immediately`, () => { - poller.start(); - expect(functionToPoll.callCount).to.be(1); - }); - }); - - describe(`when trailing is set to true`, () => { - beforeEach(() => { - poller = new Poller({ - functionToPoll, - successFunction, - errorFunction, - pollFrequencyInMillis, - trailing: true, - }); - }); - - it('waits for pollFrequencyInMillis before polling', async () => { - poller.start(); - expect(functionToPoll.callCount).to.be(0); - allowDelayPoll(pollFrequencyInMillis); - expect(functionToPoll.callCount).to.be(1); - }); - }); - - it('polls the functionToPoll multiple times', async () => { - poller.start(); - await allowPoll(pollFrequencyInMillis * 2); - expect(functionToPoll.callCount).to.be.greaterThan(1); - }); - - describe('when the function to poll succeeds', () => { - it('calls the successFunction multiple times', async () => { - poller.start(); - await allowPoll(pollFrequencyInMillis * 2); - expect(successFunction.callCount).to.be.greaterThan(1); - expect(errorFunction.callCount).to.be(0); - }); - }); - - describe('when the function to poll fails', () => { - beforeEach(() => { - functionToPoll = sinon.spy(() => { - return Promise.reject(42); - }); - }); - - describe('when the continuePollingOnError option has not been set', () => { - beforeEach(() => { - poller = new Poller({ - functionToPoll, - successFunction, - errorFunction, - pollFrequencyInMillis, - }); - }); - - it('calls the errorFunction exactly once and polling is stopped', async () => { - poller.start(); - await allowPoll(pollFrequencyInMillis * 4); - expect(poller.isRunning()).to.be(false); - expect(successFunction.callCount).to.be(0); - expect(errorFunction.callCount).to.be(1); - }); - }); - - describe('when the continuePollingOnError option has been set to true', () => { - beforeEach(() => { - poller = new Poller({ - functionToPoll, - successFunction, - errorFunction, - pollFrequencyInMillis, - continuePollingOnError: true, - }); - }); - - it('calls the errorFunction multiple times', async () => { - poller.start(); - await allowPoll(pollFrequencyInMillis); - await allowPoll(pollFrequencyInMillis); - expect(successFunction.callCount).to.be(0); - expect(errorFunction.callCount).to.be.greaterThan(1); - }); - - describe('when pollFrequencyErrorMultiplier has been set', () => { - beforeEach(() => { - poller = new Poller({ - functionToPoll, - successFunction, - errorFunction, - pollFrequencyInMillis, - continuePollingOnError: true, - pollFrequencyErrorMultiplier: 2, - }); - }); - - it('waits for the multiplier * the pollFrequency', async () => { - poller.start(); - await queueNextPoll(); - expect(functionToPoll.callCount).to.be(1); - await allowPoll(pollFrequencyInMillis); - expect(functionToPoll.callCount).to.be(1); - await allowPoll(pollFrequencyInMillis); - expect(functionToPoll.callCount).to.be(2); - }); - }); - }); - }); - }); - - describe('isRunning()', () => { - beforeEach(() => { - functionToPoll = sinon.spy(() => { - return Promise.resolve(42); - }); - poller = new Poller({ - functionToPoll, - }); - }); - - it('returns true immediately after invoking start()', () => { - poller.start(); - expect(poller.isRunning()).to.be(true); - }); - - it('returns false after invoking stop', () => { - poller.start(); - poller.stop(); - expect(poller.isRunning()).to.be(false); - }); - }); - - describe('stop()', () => { - describe(`when successFunction isn't set`, () => { - beforeEach(() => { - functionToPoll = sinon.spy(() => { - return Promise.resolve(42); - }); - poller = new Poller({ - functionToPoll, - pollFrequencyInMillis, - }); - }); - - it(`doesn't poll again`, async () => { - poller.start(); - expect(functionToPoll.callCount).to.be(1); - poller.stop(); - await allowPoll(pollFrequencyInMillis); - expect(functionToPoll.callCount).to.be(1); - }); - }); - - describe(`when successFunction is a Promise`, () => { - beforeEach(() => { - functionToPoll = sinon.spy(() => { - return Promise.resolve(42); - }); - poller = new Poller({ - functionToPoll, - successFunction: Promise.resolve(), - pollFrequencyInMillis, - }); - }); - - it(`doesn't poll again when successFunction is a Promise`, async () => { - poller.start(); - expect(functionToPoll.callCount).to.be(1); - poller.stop(); - await allowPoll(pollFrequencyInMillis); - expect(functionToPoll.callCount).to.be(1); - }); - }); - }); -}); diff --git a/x-pack/legacy/common/constants/index.ts b/x-pack/legacy/common/constants/index.ts deleted file mode 100644 index 4db0f994fd47e..0000000000000 --- a/x-pack/legacy/common/constants/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { - LICENSE_STATUS_UNAVAILABLE, - LICENSE_STATUS_INVALID, - LICENSE_STATUS_EXPIRED, - LICENSE_STATUS_VALID, -} from './license_status'; - -export { - LICENSE_TYPE_BASIC, - LICENSE_TYPE_STANDARD, - LICENSE_TYPE_GOLD, - LICENSE_TYPE_PLATINUM, - LICENSE_TYPE_ENTERPRISE, - LICENSE_TYPE_TRIAL, - RANKED_LICENSE_TYPES, - LicenseType, -} from './license_types'; diff --git a/x-pack/legacy/common/constants/license_status.ts b/x-pack/legacy/common/constants/license_status.ts deleted file mode 100644 index 5fdfa08d73959..0000000000000 --- a/x-pack/legacy/common/constants/license_status.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const LICENSE_STATUS_UNAVAILABLE = 'UNAVAILABLE'; -export const LICENSE_STATUS_INVALID = 'INVALID'; -export const LICENSE_STATUS_EXPIRED = 'EXPIRED'; -export const LICENSE_STATUS_VALID = 'VALID'; diff --git a/x-pack/legacy/common/constants/license_types.ts b/x-pack/legacy/common/constants/license_types.ts deleted file mode 100644 index 8c329df2f85f7..0000000000000 --- a/x-pack/legacy/common/constants/license_types.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const LICENSE_TYPE_BASIC = 'basic'; -export const LICENSE_TYPE_STANDARD = 'standard'; -export const LICENSE_TYPE_GOLD = 'gold'; -export const LICENSE_TYPE_PLATINUM = 'platinum'; -export const LICENSE_TYPE_ENTERPRISE = 'enterprise'; -export const LICENSE_TYPE_TRIAL = 'trial'; - -export type LicenseType = - | typeof LICENSE_TYPE_BASIC - | typeof LICENSE_TYPE_STANDARD - | typeof LICENSE_TYPE_GOLD - | typeof LICENSE_TYPE_PLATINUM - | typeof LICENSE_TYPE_ENTERPRISE - | typeof LICENSE_TYPE_TRIAL; - -// These are ordered from least featureful to most featureful, so we can assume that someone holding -// a license at a particular index cannot access any features unlocked by the licenses that follow it. -export const RANKED_LICENSE_TYPES = [ - LICENSE_TYPE_BASIC, - LICENSE_TYPE_STANDARD, - LICENSE_TYPE_GOLD, - LICENSE_TYPE_PLATINUM, - LICENSE_TYPE_ENTERPRISE, - LICENSE_TYPE_TRIAL, -]; diff --git a/x-pack/legacy/common/eui_draggable/index.d.ts b/x-pack/legacy/common/eui_draggable/index.d.ts deleted file mode 100644 index 322966b3c982e..0000000000000 --- a/x-pack/legacy/common/eui_draggable/index.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { EuiDraggable, EuiDragDropContext } from '@elastic/eui'; - -type PropsOf = T extends React.ComponentType ? ComponentProps : never; -type FirstArgumentOf = Func extends (arg1: infer FirstArgument, ...rest: any[]) => any - ? FirstArgument - : never; -export type DragHandleProps = FirstArgumentOf< - Exclude['children'], React.ReactElement> ->['dragHandleProps']; -export type DropResult = FirstArgumentOf['onDragEnd']>; diff --git a/x-pack/legacy/common/eui_styled_components/index.ts b/x-pack/legacy/common/eui_styled_components/index.ts deleted file mode 100644 index 9b3ed903627b4..0000000000000 --- a/x-pack/legacy/common/eui_styled_components/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - css, - euiStyled, - EuiTheme, - EuiThemeProvider, - createGlobalStyle, - keyframes, - withTheme, -} from './eui_styled_components'; - -export { css, euiStyled, EuiTheme, EuiThemeProvider, createGlobalStyle, keyframes, withTheme }; -// In order to to mimic the styled-components module we need to ignore the following -// eslint-disable-next-line import/no-default-export -export default euiStyled; diff --git a/x-pack/legacy/common/poller.js b/x-pack/legacy/common/poller.js deleted file mode 100644 index 09824ce9d6d23..0000000000000 --- a/x-pack/legacy/common/poller.js +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import _ from 'lodash'; - -export class Poller { - constructor(options) { - this.functionToPoll = options.functionToPoll; // Must return a Promise - this.successFunction = options.successFunction || _.noop; - this.errorFunction = options.errorFunction || _.noop; - this.pollFrequencyInMillis = options.pollFrequencyInMillis; - this.trailing = options.trailing || false; - this.continuePollingOnError = options.continuePollingOnError || false; - this.pollFrequencyErrorMultiplier = options.pollFrequencyErrorMultiplier || 1; - this._timeoutId = null; - this._isRunning = false; - } - - getPollFrequency() { - return this.pollFrequencyInMillis; - } - - _poll() { - return this.functionToPoll() - .then(this.successFunction) - .then(() => { - if (!this._isRunning) { - return; - } - - this._timeoutId = setTimeout(this._poll.bind(this), this.pollFrequencyInMillis); - }) - .catch((e) => { - this.errorFunction(e); - if (!this._isRunning) { - return; - } - - if (this.continuePollingOnError) { - this._timeoutId = setTimeout( - this._poll.bind(this), - this.pollFrequencyInMillis * this.pollFrequencyErrorMultiplier - ); - } else { - this.stop(); - } - }); - } - - start() { - if (this._isRunning) { - return; - } - - this._isRunning = true; - if (this.trailing) { - this._timeoutId = setTimeout(this._poll.bind(this), this.pollFrequencyInMillis); - } else { - this._poll(); - } - } - - stop() { - if (!this._isRunning) { - return; - } - - this._isRunning = false; - clearTimeout(this._timeoutId); - this._timeoutId = null; - } - - isRunning() { - return this._isRunning; - } -} diff --git a/x-pack/legacy/plugins/xpack_main/index.js b/x-pack/legacy/plugins/xpack_main/index.js deleted file mode 100644 index a3bd66e744fda..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/index.js +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { resolve } from 'path'; -import { setupXPackMain } from './server/lib/setup_xpack_main'; -import { xpackInfoRoute } from './server/routes/api/v1'; - -export const xpackMain = (kibana) => { - return new kibana.Plugin({ - id: 'xpack_main', - configPrefix: 'xpack.xpack_main', - publicDir: resolve(__dirname, 'public'), - require: [], - - config(Joi) { - return Joi.object({ - enabled: Joi.boolean().default(true), - }).default(); - }, - - init(server) { - setupXPackMain(server); - - // register routes - xpackInfoRoute(server); - }, - }); -}; diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/setup_xpack_main.js b/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/setup_xpack_main.js deleted file mode 100644 index f49f44bed97a7..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/setup_xpack_main.js +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { BehaviorSubject } from 'rxjs'; -import sinon from 'sinon'; -import { XPackInfo } from '../xpack_info'; -import { setupXPackMain } from '../setup_xpack_main'; - -describe('setupXPackMain()', () => { - const sandbox = sinon.createSandbox(); - - let mockServer; - let mockStatusObservable; - let mockElasticsearchPlugin; - - beforeEach(() => { - sandbox.useFakeTimers(); - - mockElasticsearchPlugin = { - getCluster: sinon.stub(), - }; - - mockStatusObservable = sinon.stub({ subscribe() {} }); - - mockServer = sinon.stub({ - plugins: { - elasticsearch: mockElasticsearchPlugin, - }, - newPlatform: { - setup: { - core: { - status: { - core$: { - pipe() { - return mockStatusObservable; - }, - }, - }, - }, - plugins: { features: {}, licensing: { license$: new BehaviorSubject() } }, - }, - }, - events: { on() {} }, - log() {}, - config() {}, - expose() {}, - ext() {}, - }); - - // Make sure plugins doesn't consume config - const configGetStub = sinon - .stub() - .throws(new Error('`config.get` is called with unexpected key.')); - mockServer.config.returns({ get: configGetStub }); - }); - - afterEach(() => sandbox.restore()); - - it('all extension hooks should be properly initialized.', () => { - setupXPackMain(mockServer); - - sinon.assert.calledWithExactly(mockServer.expose, 'info', sinon.match.instanceOf(XPackInfo)); - sinon.assert.calledWithExactly(mockStatusObservable.subscribe, sinon.match.func); - }); -}); diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/xpack_info.js b/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/xpack_info.js deleted file mode 100644 index 81fb822882817..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/xpack_info.js +++ /dev/null @@ -1,398 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { createHash } from 'crypto'; -import { BehaviorSubject } from 'rxjs'; -import expect from '@kbn/expect'; -import sinon from 'sinon'; -import { XPackInfo } from '../xpack_info'; -import { licensingMock } from '../../../../../../plugins/licensing/server/mocks'; - -function createLicense(license = {}, features = {}) { - return licensingMock.createLicense({ - license: { - uid: 'custom-uid', - type: 'gold', - mode: 'gold', - status: 'active', - expiryDateInMillis: 1286575200000, - ...license, - }, - features: { - security: { - description: 'Security for the Elastic Stack', - isAvailable: true, - isEnabled: true, - }, - watcher: { - description: 'Alerting, Notification and Automation for the Elastic Stack', - isAvailable: true, - isEnabled: false, - }, - ...features, - }, - }); -} - -function getSignature(object) { - return createHash('md5').update(JSON.stringify(object)).digest('hex'); -} - -describe('XPackInfo', () => { - let mockServer; - let mockElasticsearchPlugin; - - beforeEach(() => { - mockServer = sinon.stub({ - plugins: { elasticsearch: mockElasticsearchPlugin }, - events: { on() {} }, - newPlatform: { - setup: { - plugins: { - licensing: {}, - }, - }, - }, - }); - }); - - describe('refreshNow()', () => { - it('delegates to the new platform licensing plugin', async () => { - const refresh = sinon.spy(); - - const xPackInfo = new XPackInfo(mockServer, { - licensing: { - license$: new BehaviorSubject(createLicense()), - refresh: refresh, - }, - }); - - await xPackInfo.refreshNow(); - - sinon.assert.calledOnce(refresh); - }); - }); - - describe('license', () => { - let xPackInfo; - let license$; - beforeEach(async () => { - license$ = new BehaviorSubject(createLicense()); - xPackInfo = new XPackInfo(mockServer, { - licensing: { - license$, - refresh: () => null, - }, - }); - }); - - it('getUid() shows license uid returned from the license$.', async () => { - expect(xPackInfo.license.getUid()).to.be('custom-uid'); - - license$.next(createLicense({ uid: 'new-custom-uid' })); - - expect(xPackInfo.license.getUid()).to.be('new-custom-uid'); - - license$.next(createLicense({ uid: undefined, error: 'error-reason' })); - - expect(xPackInfo.license.getUid()).to.be(undefined); - }); - - it('isActive() is based on the status returned from the backend.', async () => { - expect(xPackInfo.license.isActive()).to.be(true); - - license$.next(createLicense({ status: 'expired' })); - expect(xPackInfo.license.isActive()).to.be(false); - - license$.next(createLicense({ status: 'some other value' })); - expect(xPackInfo.license.isActive()).to.be(false); - - license$.next(createLicense({ status: 'active' })); - expect(xPackInfo.license.isActive()).to.be(true); - - license$.next(createLicense({ status: undefined, error: 'error-reason' })); - expect(xPackInfo.license.isActive()).to.be(false); - }); - - it('getExpiryDateInMillis() is based on the value returned from the backend.', async () => { - expect(xPackInfo.license.getExpiryDateInMillis()).to.be(1286575200000); - - license$.next(createLicense({ expiryDateInMillis: 10203040 })); - expect(xPackInfo.license.getExpiryDateInMillis()).to.be(10203040); - - license$.next(createLicense({ expiryDateInMillis: undefined, error: 'error-reason' })); - expect(xPackInfo.license.getExpiryDateInMillis()).to.be(undefined); - }); - - it('getType() is based on the value returned from the backend.', async () => { - expect(xPackInfo.license.getType()).to.be('gold'); - - license$.next(createLicense({ type: 'basic' })); - expect(xPackInfo.license.getType()).to.be('basic'); - - license$.next(createLicense({ type: undefined, error: 'error-reason' })); - expect(xPackInfo.license.getType()).to.be(undefined); - }); - - it('isOneOf() correctly determines if current license is presented in the specified list.', async () => { - expect(xPackInfo.license.isOneOf('gold')).to.be(true); - expect(xPackInfo.license.isOneOf(['gold', 'basic'])).to.be(true); - expect(xPackInfo.license.isOneOf(['platinum', 'basic'])).to.be(false); - expect(xPackInfo.license.isOneOf('standard')).to.be(false); - - license$.next(createLicense({ mode: 'basic' })); - - expect(xPackInfo.license.isOneOf('basic')).to.be(true); - expect(xPackInfo.license.isOneOf(['gold', 'basic'])).to.be(true); - expect(xPackInfo.license.isOneOf(['platinum', 'gold'])).to.be(false); - expect(xPackInfo.license.isOneOf('standard')).to.be(false); - }); - }); - - describe('feature', () => { - let xPackInfo; - let license$; - beforeEach(async () => { - license$ = new BehaviorSubject( - createLicense( - {}, - { - feature: { - isAvailable: false, - isEnabled: true, - }, - } - ) - ); - xPackInfo = new XPackInfo(mockServer, { - licensing: { - license$, - refresh: () => null, - }, - }); - }); - - it('isAvailable() checks whether particular feature is available.', async () => { - const availableFeatureOne = xPackInfo.feature('security'); - const availableFeatureTwo = xPackInfo.feature('watcher'); - const unavailableFeatureOne = xPackInfo.feature('feature'); - const unavailableFeatureTwo = xPackInfo.feature('non-existing-feature'); - - expect(availableFeatureOne.isAvailable()).to.be(true); - expect(availableFeatureTwo.isAvailable()).to.be(true); - expect(unavailableFeatureOne.isAvailable()).to.be(false); - expect(unavailableFeatureTwo.isAvailable()).to.be(false); - }); - - it('isEnabled() checks whether particular feature is enabled.', async () => { - const enabledFeatureOne = xPackInfo.feature('security'); - const enabledFeatureTwo = xPackInfo.feature('feature'); - const disabledFeatureOne = xPackInfo.feature('watcher'); - const disabledFeatureTwo = xPackInfo.feature('non-existing-feature'); - - expect(enabledFeatureOne.isEnabled()).to.be(true); - expect(enabledFeatureTwo.isEnabled()).to.be(true); - expect(disabledFeatureOne.isEnabled()).to.be(false); - expect(disabledFeatureTwo.isEnabled()).to.be(false); - }); - - it('registerLicenseCheckResultsGenerator() allows to fill in XPack Info feature specific info.', async () => { - const securityFeature = xPackInfo.feature('security'); - const watcherFeature = xPackInfo.feature('watcher'); - - expect(xPackInfo.toJSON().features.security).to.be(undefined); - expect(xPackInfo.toJSON().features.watcher).to.be(undefined); - - securityFeature.registerLicenseCheckResultsGenerator((info) => { - return { - isXPackInfo: info instanceof XPackInfo, - license: info.license.getType(), - someCustomValue: 100500, - }; - }); - - expect(xPackInfo.toJSON().features.security).to.eql({ - isXPackInfo: true, - license: 'gold', - someCustomValue: 100500, - }); - expect(xPackInfo.toJSON().features.watcher).to.be(undefined); - - watcherFeature.registerLicenseCheckResultsGenerator((info) => { - return { - isXPackInfo: info instanceof XPackInfo, - license: info.license.getType(), - someAnotherCustomValue: 500100, - }; - }); - - expect(xPackInfo.toJSON().features.security).to.eql({ - isXPackInfo: true, - license: 'gold', - someCustomValue: 100500, - }); - expect(xPackInfo.toJSON().features.watcher).to.eql({ - isXPackInfo: true, - license: 'gold', - someAnotherCustomValue: 500100, - }); - - license$.next(createLicense({ type: 'platinum' })); - - expect(xPackInfo.toJSON().features.security).to.eql({ - isXPackInfo: true, - license: 'platinum', - someCustomValue: 100500, - }); - expect(xPackInfo.toJSON().features.watcher).to.eql({ - isXPackInfo: true, - license: 'platinum', - someAnotherCustomValue: 500100, - }); - }); - - it('getLicenseCheckResults() correctly returns feature specific info.', async () => { - const securityFeature = xPackInfo.feature('security'); - const watcherFeature = xPackInfo.feature('watcher'); - - expect(securityFeature.getLicenseCheckResults()).to.be(undefined); - expect(watcherFeature.getLicenseCheckResults()).to.be(undefined); - - securityFeature.registerLicenseCheckResultsGenerator((info) => { - return { - isXPackInfo: info instanceof XPackInfo, - license: info.license.getType(), - someCustomValue: 100500, - }; - }); - - expect(securityFeature.getLicenseCheckResults()).to.eql({ - isXPackInfo: true, - license: 'gold', - someCustomValue: 100500, - }); - expect(watcherFeature.getLicenseCheckResults()).to.be(undefined); - - watcherFeature.registerLicenseCheckResultsGenerator((info) => { - return { - isXPackInfo: info instanceof XPackInfo, - license: info.license.getType(), - someAnotherCustomValue: 500100, - }; - }); - - expect(securityFeature.getLicenseCheckResults()).to.eql({ - isXPackInfo: true, - license: 'gold', - someCustomValue: 100500, - }); - expect(watcherFeature.getLicenseCheckResults()).to.eql({ - isXPackInfo: true, - license: 'gold', - someAnotherCustomValue: 500100, - }); - - license$.next(createLicense({ type: 'platinum' })); - - expect(securityFeature.getLicenseCheckResults()).to.eql({ - isXPackInfo: true, - license: 'platinum', - someCustomValue: 100500, - }); - expect(watcherFeature.getLicenseCheckResults()).to.eql({ - isXPackInfo: true, - license: 'platinum', - someAnotherCustomValue: 500100, - }); - }); - }); - - it('onLicenseInfoChange() allows to subscribe to license update', async () => { - const license$ = new BehaviorSubject(createLicense()); - - const xPackInfo = new XPackInfo(mockServer, { - licensing: { - license$, - refresh: () => null, - }, - }); - - const watcherFeature = xPackInfo.feature('watcher'); - watcherFeature.registerLicenseCheckResultsGenerator((info) => ({ - type: info.license.getType(), - })); - - const statuses = []; - xPackInfo.onLicenseInfoChange(() => statuses.push(watcherFeature.getLicenseCheckResults())); - - license$.next(createLicense({ type: 'basic' })); - expect(statuses).to.eql([{ type: 'basic' }]); - - license$.next(createLicense({ type: 'trial' })); - expect(statuses).to.eql([{ type: 'basic' }, { type: 'trial' }]); - }); - - it('refreshNow() leads to onLicenseInfoChange()', async () => { - const license$ = new BehaviorSubject(createLicense()); - - const xPackInfo = new XPackInfo(mockServer, { - licensing: { - license$, - refresh: () => license$.next({ type: 'basic' }), - }, - }); - - const watcherFeature = xPackInfo.feature('watcher'); - - watcherFeature.registerLicenseCheckResultsGenerator((info) => ({ - type: info.license.getType(), - })); - - const statuses = []; - xPackInfo.onLicenseInfoChange(() => statuses.push(watcherFeature.getLicenseCheckResults())); - - await xPackInfo.refreshNow(); - expect(statuses).to.eql([{ type: 'basic' }]); - }); - - it('getSignature() returns correct signature.', async () => { - const license$ = new BehaviorSubject(createLicense()); - const xPackInfo = new XPackInfo(mockServer, { - licensing: { - license$, - refresh: () => null, - }, - }); - - expect(xPackInfo.getSignature()).to.be( - getSignature({ - license: { - type: 'gold', - isActive: true, - expiryDateInMillis: 1286575200000, - }, - features: {}, - }) - ); - - license$.next(createLicense({ type: 'platinum', expiryDateInMillis: 20304050 })); - - const expectedSignature = getSignature({ - license: { - type: 'platinum', - isActive: true, - expiryDateInMillis: 20304050, - }, - features: {}, - }); - expect(xPackInfo.getSignature()).to.be(expectedSignature); - - // Should stay the same after refresh if nothing changed. - license$.next(createLicense({ type: 'platinum', expiryDateInMillis: 20304050 })); - - expect(xPackInfo.getSignature()).to.be(expectedSignature); - }); -}); diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/setup_xpack_main.js b/x-pack/legacy/plugins/xpack_main/server/lib/setup_xpack_main.js deleted file mode 100644 index fd4e3c86d0ca7..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/lib/setup_xpack_main.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { pairwise } from 'rxjs/operators'; -import { XPackInfo } from './xpack_info'; - -/** - * Setup the X-Pack Main plugin. This is fired every time that the Elasticsearch plugin becomes Green. - * - * This will ensure that X-Pack is installed on the Elasticsearch cluster, as well as trigger the initial - * polling for _xpack/info. - * - * @param server {Object} The Kibana server object. - */ -export function setupXPackMain(server) { - const info = new XPackInfo(server, { licensing: server.newPlatform.setup.plugins.licensing }); - - server.expose('info', info); - - // trigger an xpack info refresh whenever the elasticsearch plugin status changes - server.newPlatform.setup.core.status.core$ - .pipe(pairwise()) - .subscribe(async ([coreLast, coreCurrent]) => { - if (coreLast.elasticsearch.level !== coreCurrent.elasticsearch.level) { - await info.refreshNow(); - } - }); - - return info; -} diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info.ts b/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info.ts deleted file mode 100644 index aa66532a2897d..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info.ts +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { createHash } from 'crypto'; -import { Legacy } from 'kibana'; - -import { XPackInfoLicense } from './xpack_info_license'; - -import { LicensingPluginSetup, ILicense } from '../../../../../plugins/licensing/server'; - -export interface XPackInfoOptions { - clusterSource?: string; - pollFrequencyInMillis: number; -} - -type LicenseGeneratorCheck = (xpackInfo: XPackInfo) => any; - -export interface XPackFeature { - isAvailable(): boolean; - isEnabled(): boolean; - registerLicenseCheckResultsGenerator(generator: LicenseGeneratorCheck): void; - getLicenseCheckResults(): any; -} - -interface Deps { - licensing: LicensingPluginSetup; -} - -/** - * A helper that provides a convenient way to access XPack Info returned by Elasticsearch. - */ -export class XPackInfo { - /** - * XPack License object. - * @type {XPackInfoLicense} - * @private - */ - _license: XPackInfoLicense; - - /** - * Feature name <-> feature license check generator function mapping. - * @type {Map} - * @private - */ - _featureLicenseCheckResultsGenerators = new Map(); - - /** - * Set of listener functions that will be called whenever the license - * info changes - * @type {Set} - */ - _licenseInfoChangedListeners = new Set<() => void>(); - - /** - * Cache that may contain last xpack info API response or error, json representation - * of xpack info and xpack info signature. - * @type {{response: Object|undefined, error: Object|undefined, json: Object|undefined, signature: string|undefined}} - * @private - */ - private _cache: { - license?: ILicense; - error?: string; - json?: Record; - signature?: string; - }; - - /** - * XPack License instance. - * @returns {XPackInfoLicense} - */ - public get license() { - return this._license; - } - - private readonly licensingPlugin: LicensingPluginSetup; - - /** - * Constructs XPack info object. - * @param {Hapi.Server} server HapiJS server instance. - */ - constructor(server: Legacy.Server, deps: Deps) { - if (!deps.licensing) { - throw new Error('XPackInfo requires enabled Licensing plugin'); - } - this.licensingPlugin = deps.licensing; - - this._cache = {}; - - this.licensingPlugin.license$.subscribe((license: ILicense) => { - if (license.isActive) { - this._cache = { - license, - error: undefined, - }; - } else { - this._cache = { - license, - error: license.error, - }; - } - - this._licenseInfoChangedListeners.forEach((fn) => fn()); - }); - - this._license = new XPackInfoLicense(() => this._cache.license); - } - - /** - * Checks whether XPack info is available. - * @returns {boolean} - */ - isAvailable() { - return Boolean(this._cache.license?.isAvailable); - } - - /** - * Checks whether ES was available - * @returns {boolean} - */ - isXpackUnavailable() { - return ( - this._cache.error && - this._cache.error === 'X-Pack plugin is not installed on the Elasticsearch cluster.' - ); - } - - /** - * If present, describes the reason why XPack info is not available. - * @returns {Error|string} - */ - unavailableReason() { - return this._cache.license?.getUnavailableReason(); - } - - onLicenseInfoChange(handler: () => void) { - this._licenseInfoChangedListeners.add(handler); - } - - /** - * Queries server to get the updated XPack info. - * @returns {Promise.} - */ - async refreshNow() { - await this.licensingPlugin.refresh(); - return this; - } - - /** - * Returns a wrapper around XPack info that gives an access to the properties of - * the specific feature. - * @param {string} name Name of the feature to get a wrapper for. - * @returns {Object} - */ - feature(name: string): XPackFeature { - return { - /** - * Checks whether feature is available (permitted by the current license). - * @returns {boolean} - */ - isAvailable: () => { - return Boolean(this._cache.license?.getFeature(name).isAvailable); - }, - - /** - * Checks whether feature is enabled (not disabled by the configuration specifically). - * @returns {boolean} - */ - isEnabled: () => { - return Boolean(this._cache.license?.getFeature(name).isEnabled); - }, - - /** - * Registers a `generator` function that will be called with XPackInfo instance as - * argument whenever XPack info changes. Whatever `generator` returns will be stored - * in XPackInfo JSON representation and can be accessed with `getLicenseCheckResults`. - * @param {Function} generator Function to call whenever XPackInfo changes. - */ - registerLicenseCheckResultsGenerator: (generator: LicenseGeneratorCheck) => { - this._featureLicenseCheckResultsGenerators.set(name, generator); - - // Since JSON representation and signature are cached we should invalidate them to - // include results from newly registered generator when they are requested. - this._cache.json = undefined; - this._cache.signature = undefined; - }, - - /** - * Returns license check results that were previously produced by the `generator` function. - * @returns {Object} - */ - getLicenseCheckResults: () => this.toJSON().features[name], - }; - } - - /** - * Extracts string md5 hash from the stringified version of license JSON representation. - * @returns {string} - */ - getSignature() { - if (this._cache.signature) { - return this._cache.signature; - } - - this._cache.signature = createHash('md5').update(JSON.stringify(this.toJSON())).digest('hex'); - - return this._cache.signature; - } - - /** - * Returns JSON representation of the license object that is suitable for serialization. - * @returns {Object} - */ - toJSON() { - if (this._cache.json) { - return this._cache.json; - } - - this._cache.json = { - license: { - type: this.license.getType(), - isActive: this.license.isActive(), - expiryDateInMillis: this.license.getExpiryDateInMillis(), - }, - features: {}, - }; - - // Set response elements specific to each feature. To do this, - // call the license check results generator for each feature, passing them - // the xpack info object - for (const [feature, licenseChecker] of this._featureLicenseCheckResultsGenerators) { - // return value expected to be a dictionary object. - this._cache.json.features[feature] = licenseChecker(this); - } - - return this._cache.json; - } -} diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info_license.test.js b/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info_license.test.js deleted file mode 100644 index ccb5742216ca7..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info_license.test.js +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { licensingMock } from '../../../../../plugins/licensing/server/mocks'; -import { XPackInfoLicense } from './xpack_info_license'; - -function getXPackInfoLicense(getRawLicense) { - return new XPackInfoLicense(getRawLicense); -} - -describe('XPackInfoLicense', () => { - const xpackInfoLicenseUndefined = getXPackInfoLicense(() => {}); - let xpackInfoLicense; - let getRawLicense; - - beforeEach(() => { - getRawLicense = jest.fn(); - xpackInfoLicense = getXPackInfoLicense(getRawLicense); - }); - - test('getUid returns uid field', () => { - const uid = 'abc123'; - - getRawLicense.mockReturnValue(licensingMock.createLicense({ license: { uid } })); - - expect(xpackInfoLicense.getUid()).toBe(uid); - expect(getRawLicense).toHaveBeenCalledTimes(1); - - expect(xpackInfoLicenseUndefined.getUid()).toBe(undefined); - }); - - test('isActive returns true if status is active', () => { - getRawLicense.mockReturnValue(licensingMock.createLicense({ license: { status: 'active' } })); - - expect(xpackInfoLicense.isActive()).toBe(true); - expect(getRawLicense).toHaveBeenCalledTimes(1); - }); - - test('isActive returns false if status is not active', () => { - getRawLicense.mockReturnValue(licensingMock.createLicense({ license: { status: 'aCtIvE' } })); // needs to match exactly - - expect(xpackInfoLicense.isActive()).toBe(false); - expect(getRawLicense).toHaveBeenCalledTimes(1); - - expect(xpackInfoLicenseUndefined.isActive()).toBe(false); - }); - - test('getExpiryDateInMillis returns expiry_date_in_millis', () => { - getRawLicense.mockReturnValue( - licensingMock.createLicense({ license: { expiryDateInMillis: 123 } }) - ); - - expect(xpackInfoLicense.getExpiryDateInMillis()).toBe(123); - expect(getRawLicense).toHaveBeenCalledTimes(1); - - expect(xpackInfoLicenseUndefined.getExpiryDateInMillis()).toBe(undefined); - }); - - test('isOneOf returns true of the mode includes one of the types', () => { - getRawLicense.mockReturnValue(licensingMock.createLicense({ license: { mode: 'platinum' } })); - - expect(xpackInfoLicense.isOneOf('platinum')).toBe(true); - expect(getRawLicense).toHaveBeenCalledTimes(1); - - expect(xpackInfoLicense.isOneOf(['platinum'])).toBe(true); - expect(getRawLicense).toHaveBeenCalledTimes(2); - expect(xpackInfoLicense.isOneOf(['gold', 'platinum'])).toBe(true); - expect(getRawLicense).toHaveBeenCalledTimes(3); - expect(xpackInfoLicense.isOneOf(['platinum', 'gold'])).toBe(true); - expect(getRawLicense).toHaveBeenCalledTimes(4); - expect(xpackInfoLicense.isOneOf(['basic', 'gold'])).toBe(false); - expect(getRawLicense).toHaveBeenCalledTimes(5); - expect(xpackInfoLicense.isOneOf(['basic'])).toBe(false); - expect(getRawLicense).toHaveBeenCalledTimes(6); - - expect(xpackInfoLicenseUndefined.isOneOf(['platinum', 'gold'])).toBe(false); - }); - - test('getType returns the type', () => { - getRawLicense.mockReturnValue(licensingMock.createLicense({ license: { type: 'basic' } })); - - expect(xpackInfoLicense.getType()).toBe('basic'); - expect(getRawLicense).toHaveBeenCalledTimes(1); - - getRawLicense.mockReturnValue(licensingMock.createLicense({ license: { type: 'gold' } })); - - expect(xpackInfoLicense.getType()).toBe('gold'); - expect(getRawLicense).toHaveBeenCalledTimes(2); - - expect(xpackInfoLicenseUndefined.getType()).toBe(undefined); - }); - - test('getMode returns the mode', () => { - getRawLicense.mockReturnValue(licensingMock.createLicense({ license: { mode: 'basic' } })); - - expect(xpackInfoLicense.getMode()).toBe('basic'); - expect(getRawLicense).toHaveBeenCalledTimes(1); - - getRawLicense.mockReturnValue(licensingMock.createLicense({ license: { mode: 'gold' } })); - - expect(xpackInfoLicense.getMode()).toBe('gold'); - expect(getRawLicense).toHaveBeenCalledTimes(2); - - expect(xpackInfoLicenseUndefined.getMode()).toBe(undefined); - }); - - test('isActiveLicense returns the true if active and typeChecker matches', () => { - const expectAbc123 = (type) => type === 'abc123'; - - getRawLicense.mockReturnValue( - licensingMock.createLicense({ license: { status: 'active', mode: 'abc123' } }) - ); - - expect(xpackInfoLicense.isActiveLicense(expectAbc123)).toBe(true); - expect(getRawLicense).toHaveBeenCalledTimes(1); - - getRawLicense.mockReturnValue( - licensingMock.createLicense({ license: { status: 'NOTactive', mode: 'abc123' } }) - ); - - expect(xpackInfoLicense.isActiveLicense(expectAbc123)).toBe(false); - expect(getRawLicense).toHaveBeenCalledTimes(2); - - getRawLicense.mockReturnValue( - licensingMock.createLicense({ license: { status: 'NOTactive', mode: 'NOTabc123' } }) - ); - - expect(xpackInfoLicense.isActiveLicense(expectAbc123)).toBe(false); - expect(getRawLicense).toHaveBeenCalledTimes(3); - - getRawLicense.mockReturnValue( - licensingMock.createLicense({ license: { status: 'active', mode: 'NOTabc123' } }) - ); - - expect(xpackInfoLicense.isActiveLicense(expectAbc123)).toBe(false); - expect(getRawLicense).toHaveBeenCalledTimes(4); - - expect(xpackInfoLicenseUndefined.isActive(expectAbc123)).toBe(false); - }); - - test('isBasic returns the true if active and basic', () => { - getRawLicense.mockReturnValue( - licensingMock.createLicense({ license: { status: 'active', mode: 'basic' } }) - ); - - expect(xpackInfoLicense.isBasic()).toBe(true); - expect(getRawLicense).toHaveBeenCalledTimes(1); - - getRawLicense.mockReturnValue( - licensingMock.createLicense({ license: { status: 'NOTactive', mode: 'gold' } }) - ); - - expect(xpackInfoLicense.isBasic()).toBe(false); - expect(getRawLicense).toHaveBeenCalledTimes(2); - - getRawLicense.mockReturnValue( - licensingMock.createLicense({ license: { status: 'NOTactive', mode: 'trial' } }) - ); - - expect(xpackInfoLicense.isBasic()).toBe(false); - expect(getRawLicense).toHaveBeenCalledTimes(3); - - getRawLicense.mockReturnValue( - licensingMock.createLicense({ license: { status: 'active', mode: 'platinum' } }) - ); - - expect(xpackInfoLicense.isBasic()).toBe(false); - expect(getRawLicense).toHaveBeenCalledTimes(4); - - expect(xpackInfoLicenseUndefined.isBasic()).toBe(false); - }); - - test('isNotBasic returns the true if active and not basic', () => { - getRawLicense.mockReturnValue( - licensingMock.createLicense({ license: { status: 'active', mode: 'platinum' } }) - ); - - expect(xpackInfoLicense.isNotBasic()).toBe(true); - expect(getRawLicense).toHaveBeenCalledTimes(1); - - getRawLicense.mockReturnValue( - licensingMock.createLicense({ license: { status: 'NOTactive', mode: 'gold' } }) - ); - - expect(xpackInfoLicense.isNotBasic()).toBe(false); - expect(getRawLicense).toHaveBeenCalledTimes(2); - - getRawLicense.mockReturnValue( - licensingMock.createLicense({ license: { status: 'NOTactive', mode: 'trial' } }) - ); - - expect(xpackInfoLicense.isNotBasic()).toBe(false); - expect(getRawLicense).toHaveBeenCalledTimes(3); - - getRawLicense.mockReturnValue( - licensingMock.createLicense({ license: { status: 'active', mode: 'basic' } }) - ); - - expect(xpackInfoLicense.isNotBasic()).toBe(false); - expect(getRawLicense).toHaveBeenCalledTimes(4); - - expect(xpackInfoLicenseUndefined.isNotBasic()).toBe(false); - }); -}); diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info_license.ts b/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info_license.ts deleted file mode 100644 index dd53f63909475..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/lib/xpack_info_license.ts +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { ILicense } from '../../../../../plugins/licensing/server'; - -/** - * "View" for XPack Info license information. - */ -export class XPackInfoLicense { - /** - * Function that retrieves license information from the XPack info object. - * @type {Function} - * @private - */ - _getRawLicense: () => ILicense | undefined; - - constructor(getRawLicense: () => ILicense | undefined) { - this._getRawLicense = getRawLicense; - } - - /** - * Returns unique identifier of the license. - * @returns {string|undefined} - */ - getUid() { - return this._getRawLicense()?.uid; - } - - /** - * Indicates whether license is still active. - * @returns {boolean} - */ - isActive() { - return Boolean(this._getRawLicense()?.isActive); - } - - /** - * Returns license expiration date in ms. - * - * Note: A basic license created after 6.3 will have no expiration, thus returning undefined. - * - * @returns {number|undefined} - */ - getExpiryDateInMillis() { - return this._getRawLicense()?.expiryDateInMillis; - } - - /** - * Checks if the license is represented in a specified license list. - * @param {String} candidateLicenses List of the licenses to check against. - * @returns {boolean} - */ - isOneOf(candidateLicenses: string | string[]) { - const candidates = Array.isArray(candidateLicenses) ? candidateLicenses : [candidateLicenses]; - const mode = this._getRawLicense()?.mode; - return Boolean(mode && candidates.includes(mode)); - } - - /** - * Returns type of the license (basic, gold etc.). - * @returns {string|undefined} - */ - getType() { - return this._getRawLicense()?.type; - } - - /** - * Returns mode of the license (basic, gold etc.). This is the "effective" type of the license. - * @returns {string|undefined} - */ - getMode() { - return this._getRawLicense()?.mode; - } - - /** - * Determine if the current license is active and the supplied {@code type}. - * - * @param {Function} typeChecker The license type checker. - * @returns {boolean} - */ - isActiveLicense(typeChecker: (mode: string) => boolean) { - const license = this._getRawLicense(); - - return Boolean(license?.isActive && typeChecker(license.mode as any)); - } - - /** - * Determine if the license is an active, basic license. - * - * Note: This also verifies that the license is active. Therefore it is not safe to assume that !isBasic() === isNotBasic(). - * - * @returns {boolean} - */ - isBasic() { - return this.isActiveLicense((mode) => mode === 'basic'); - } - - /** - * Determine if the license is an active, non-basic license (e.g., standard, gold, platinum, or trial). - * - * Note: This also verifies that the license is active. Therefore it is not safe to assume that !isBasic() === isNotBasic(). - * - * @returns {boolean} - */ - isNotBasic() { - return this.isActiveLicense((mode) => mode !== 'basic'); - } -} diff --git a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/__tests__/xpack_info.js b/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/__tests__/xpack_info.js deleted file mode 100644 index 540d9f63ea6c8..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/__tests__/xpack_info.js +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import sinon from 'sinon'; - -import { xpackInfoRoute } from '../xpack_info'; - -describe('XPackInfo routes', () => { - let serverStub; - beforeEach(() => { - serverStub = { - route: sinon.stub(), - plugins: { - xpack_main: { - info: sinon.stub({ isAvailable() {}, toJSON() {} }), - }, - }, - }; - - xpackInfoRoute(serverStub); - }); - - it('correctly initialize XPack Info route.', () => { - sinon.assert.calledWithExactly(serverStub.route, { - method: 'GET', - path: '/api/xpack/v1/info', - handler: sinon.match.func, - }); - }); - - it('replies with `Not Found` Boom error if `xpackInfo` is not available.', () => { - serverStub.plugins.xpack_main.info.isAvailable.returns(false); - - const onRouteHandler = serverStub.route.firstCall.args[0].handler; - const response = onRouteHandler(); - - expect(response.isBoom).to.be(true); - expect(response.message).to.be('Not Found'); - expect(response.output.statusCode).to.be(404); - }); - - it('replies with pre-processed `xpackInfo` if it is available.', () => { - serverStub.plugins.xpack_main.info.isAvailable.returns(true); - serverStub.plugins.xpack_main.info.toJSON.returns({ - license: { - type: 'gold', - isActive: true, - expiryDateInMillis: 1509368280381, - }, - features: { - security: { - showLogin: true, - allowLogin: true, - showLinks: false, - allowRoleDocumentLevelSecurity: false, - allowRoleFieldLevelSecurity: false, - }, - }, - }); - - const onRouteHandler = serverStub.route.firstCall.args[0].handler; - const response = onRouteHandler(); - - expect(response).to.eql({ - license: { - type: 'gold', - is_active: true, - expiry_date_in_millis: 1509368280381, - }, - features: { - security: { - show_login: true, - allow_login: true, - show_links: false, - allow_role_document_level_security: false, - allow_role_field_level_security: false, - }, - }, - }); - }); -}); diff --git a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/xpack_info.js b/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/xpack_info.js deleted file mode 100644 index 3cc57ae9fcab4..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/xpack_info.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Boom from 'boom'; -import { convertKeysToSnakeCaseDeep } from '../../../../../../server/lib/key_case_converter'; - -/* - * A route to provide the basic XPack info for the production cluster - */ -export function xpackInfoRoute(server) { - server.route({ - method: 'GET', - path: '/api/xpack/v1/info', - handler() { - const xPackInfo = server.plugins.xpack_main.info; - - return xPackInfo.isAvailable() - ? convertKeysToSnakeCaseDeep(xPackInfo.toJSON()) - : Boom.notFound(); - }, - }); -} diff --git a/x-pack/legacy/plugins/xpack_main/server/xpack_main.d.ts b/x-pack/legacy/plugins/xpack_main/server/xpack_main.d.ts deleted file mode 100644 index c2ec5662ad12e..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/xpack_main.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import KbnServer from 'src/legacy/server/kbn_server'; -import { KibanaFeature } from '../../../../plugins/features/server'; -import { XPackInfo, XPackInfoOptions } from './lib/xpack_info'; -export { XPackFeature } from './lib/xpack_info'; - -export interface XPackMainPlugin { - info: XPackInfo; -} diff --git a/x-pack/legacy/server/lib/__tests__/key_case_converter.js b/x-pack/legacy/server/lib/__tests__/key_case_converter.js deleted file mode 100644 index 7ed9fa668ae66..0000000000000 --- a/x-pack/legacy/server/lib/__tests__/key_case_converter.js +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { convertKeysToSnakeCaseDeep, convertKeysToCamelCaseDeep } from '../key_case_converter'; - -describe('key_case_converter', () => { - let testObject; - - beforeEach(() => { - testObject = { - topLevelKey1: { - innerLevelKey1: 17, - inner_level_key2: [19, 31], - }, - top_level_key2: { - innerLevelKey1: 'foo_fooFoo', - inner_level_key2: [{ foo_bar: 29 }, { barBar: 37 }], - }, - }; - }); - - describe('convertKeysToSnakeCaseDeep', () => { - it('should recursively convert camelCase keys to snake_case keys', () => { - const expectedResultObject = { - top_level_key_1: { - inner_level_key_1: 17, - inner_level_key_2: [19, 31], - }, - top_level_key_2: { - inner_level_key_1: 'foo_fooFoo', - inner_level_key_2: [{ foo_bar: 29 }, { bar_bar: 37 }], - }, - }; - expect(convertKeysToSnakeCaseDeep(testObject)).to.eql(expectedResultObject); - }); - - it('should not modify original object', () => { - convertKeysToSnakeCaseDeep(testObject); - expect(Object.keys(testObject)).to.contain('topLevelKey1'); - expect(Object.keys(testObject.topLevelKey1)).to.contain('innerLevelKey1'); - }); - - it('should preserve inner arrays', () => { - const result = convertKeysToSnakeCaseDeep(testObject); - expect(testObject.topLevelKey1.inner_level_key2).to.be.an(Array); - expect(result.top_level_key_1.inner_level_key_2).to.be.an(Array); - }); - - it('should preserve top-level arrays', () => { - testObject = [{ foo_bar: 17 }, [19, { barBaz: 'qux' }]]; - const expectedResultObject = [{ foo_bar: 17 }, [19, { bar_baz: 'qux' }]]; - const result = convertKeysToSnakeCaseDeep(testObject); - expect(testObject).to.be.an(Array); - expect(testObject[1]).to.be.an(Array); - expect(result).to.be.an(Array); - expect(result[1]).to.be.an(Array); - expect(result).to.eql(expectedResultObject); - }); - - it('should throw an error if something other an object or array is passed in', () => { - const expectedErrorMessageRegexp = /Specified object should be an Object or Array/; - expect(convertKeysToSnakeCaseDeep) - .withArgs('neither an object nor an array') - .to.throwException(expectedErrorMessageRegexp); - }); - }); - - describe('convertKeysToCamelCaseDeep', () => { - it('should recursively convert snake_case keys to camelCase keys', () => { - const expectedResultObject = { - topLevelKey1: { - innerLevelKey1: 17, - innerLevelKey2: [19, 31], - }, - topLevelKey2: { - innerLevelKey1: 'foo_fooFoo', - innerLevelKey2: [{ fooBar: 29 }, { barBar: 37 }], - }, - }; - expect(convertKeysToCamelCaseDeep(testObject)).to.eql(expectedResultObject); - }); - - it('should not modify original object', () => { - convertKeysToCamelCaseDeep(testObject); - expect(Object.keys(testObject)).to.contain('top_level_key2'); - expect(Object.keys(testObject.topLevelKey1)).to.contain('inner_level_key2'); - }); - - it('should preserve inner arrays', () => { - const result = convertKeysToCamelCaseDeep(testObject); - expect(testObject.topLevelKey1.inner_level_key2).to.be.an(Array); - expect(result.topLevelKey1.innerLevelKey2).to.be.an(Array); - }); - - it('should preserve top-level arrays', () => { - testObject = [{ foo_bar: 17 }, [19, { barBaz: 'qux' }]]; - const expectedResultObject = [{ fooBar: 17 }, [19, { barBaz: 'qux' }]]; - const result = convertKeysToCamelCaseDeep(testObject); - expect(testObject).to.be.an(Array); - expect(testObject[1]).to.be.an(Array); - expect(result).to.be.an(Array); - expect(result[1]).to.be.an(Array); - expect(result).to.eql(expectedResultObject); - }); - - it('should throw an error if something other an object or array is passed in', () => { - const expectedErrorMessageRegexp = /Specified object should be an Object or Array/; - expect(convertKeysToCamelCaseDeep) - .withArgs('neither an object nor an array') - .to.throwException(expectedErrorMessageRegexp); - }); - }); -}); diff --git a/x-pack/legacy/server/lib/__tests__/kibana_state.js b/x-pack/legacy/server/lib/__tests__/kibana_state.js deleted file mode 100644 index d1b4142b10446..0000000000000 --- a/x-pack/legacy/server/lib/__tests__/kibana_state.js +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import rison from 'rison-node'; -import { parseKibanaState } from '../parse_kibana_state'; - -const stateIndices = { - global: '_g', - app: '_a', -}; -const globalTime = - '(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-15m,mode:quick,to:now))'; - -describe('Kibana state', function () { - describe('type checking', function () { - it('should throw if not given an object', function () { - const fn = () => parseKibanaState('i am not an object', 'global'); - const fn2 = () => parseKibanaState(['arrays are not valid either'], 'global'); - expect(fn).to.throwException(/must be an object/i); - expect(fn2).to.throwException(/must be an object/i); - }); - - it('should throw with invalid type', function () { - const fn = () => parseKibanaState({}, 'this is an invalid state type'); - expect(fn).to.throwException(/unknown state type/i); - }); - }); - - describe('value of exists', function () { - it('should be false if state does not exist', function () { - const state = parseKibanaState({}, 'global'); - expect(state.exists).to.equal(false); - }); - - it('should be true if state exists', function () { - const query = {}; - query[stateIndices.global] = rison.encode({ hello: 'world' }); - const state = parseKibanaState(query, 'global'); - expect(state.exists).to.equal(true); - }); - }); - - describe('instance methods', function () { - let query; - - beforeEach(function () { - query = {}; - query[stateIndices.global] = globalTime; - }); - - describe('get', function () { - it('should return the value', function () { - const state = parseKibanaState(query, 'global'); - const { refreshInterval } = rison.decode(globalTime); - expect(state.get('refreshInterval')).to.eql(refreshInterval); - }); - - it('should use the default value for missing props', function () { - const defaultValue = 'default value'; - const state = parseKibanaState(query, 'global'); - expect(state.get('no such value', defaultValue)).to.equal(defaultValue); - }); - }); - - describe('set', function () { - it('should update the value of the state', function () { - const state = parseKibanaState(query, 'global'); - expect(state.get('refreshInterval.pause')).to.equal(false); - - state.set(['refreshInterval', 'pause'], true); - expect(state.get('refreshInterval.pause')).to.equal(true); - }); - - it('should create new properties', function () { - const prop = 'newProp'; - const value = 12345; - const state = parseKibanaState(query, 'global'); - expect(state.get(prop)).to.be(undefined); - - state.set(prop, value); - expect(state.get(prop)).to.not.be(undefined); - expect(state.get(prop)).to.equal(value); - }); - }); - - describe('removing properties', function () { - it('should remove a single value', function () { - const state = parseKibanaState(query, 'global'); - expect(state.get('refreshInterval')).to.be.an('object'); - - state.removeProps('refreshInterval'); - expect(state.get('refreshInterval')).to.be(undefined); - }); - - it('should remove multiple values', function () { - const state = parseKibanaState(query, 'global'); - expect(state.get('refreshInterval')).to.be.an('object'); - expect(state.get('time')).to.be.an('object'); - - state.removeProps(['refreshInterval', 'time']); - expect(state.get('refreshInterval')).to.be(undefined); - expect(state.get('time')).to.be(undefined); - }); - }); - - describe('toString', function () { - it('should rison encode the state', function () { - const state = parseKibanaState(query, 'global'); - expect(state.toString()).to.equal(globalTime); - }); - }); - - describe('toQuery', function () { - it('should return an object', function () { - const state = parseKibanaState(query, 'global'); - expect(state.toQuery()).to.be.an('object'); - }); - - it('should contain the kibana state property', function () { - const state = parseKibanaState(query, 'global'); - expect(state.toQuery()).to.have.property(stateIndices.global, globalTime); - }); - }); - }); -}); diff --git a/x-pack/legacy/server/lib/check_license/check_license.js b/x-pack/legacy/server/lib/check_license/check_license.js deleted file mode 100644 index 7695755622310..0000000000000 --- a/x-pack/legacy/server/lib/check_license/check_license.js +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; -import { - LICENSE_STATUS_UNAVAILABLE, - LICENSE_STATUS_INVALID, - LICENSE_STATUS_EXPIRED, - LICENSE_STATUS_VALID, - RANKED_LICENSE_TYPES, -} from '../../../common/constants'; - -export function checkLicense(pluginName, minimumLicenseRequired, xpackLicenseInfo) { - if (!minimumLicenseRequired) { - throw new Error( - `Error checking license for plugin "${pluginName}". The minimum license required has not been provided.` - ); - } - - if (!RANKED_LICENSE_TYPES.includes(minimumLicenseRequired)) { - throw new Error(`Invalid license type supplied to checkLicense: ${minimumLicenseRequired}`); - } - - // If, for some reason, we cannot get the license information - // from Elasticsearch, assume worst case and disable - if (!xpackLicenseInfo || !xpackLicenseInfo.isAvailable()) { - return { - status: LICENSE_STATUS_UNAVAILABLE, - message: i18n.translate('xpack.server.checkLicense.errorUnavailableMessage', { - defaultMessage: - 'You cannot use {pluginName} because license information is not available at this time.', - values: { pluginName }, - }), - }; - } - - const { license } = xpackLicenseInfo; - const isLicenseModeValid = license.isOneOf( - [...RANKED_LICENSE_TYPES].splice(RANKED_LICENSE_TYPES.indexOf(minimumLicenseRequired)) - ); - const isLicenseActive = license.isActive(); - const licenseType = license.getType(); - - // License is not valid - if (!isLicenseModeValid) { - return { - status: LICENSE_STATUS_INVALID, - message: i18n.translate('xpack.server.checkLicense.errorUnsupportedMessage', { - defaultMessage: - 'Your {licenseType} license does not support {pluginName}. Please upgrade your license.', - values: { licenseType, pluginName }, - }), - }; - } - - // License is valid but not active - if (!isLicenseActive) { - return { - status: LICENSE_STATUS_EXPIRED, - message: i18n.translate('xpack.server.checkLicense.errorExpiredMessage', { - defaultMessage: - 'You cannot use {pluginName} because your {licenseType} license has expired.', - values: { licenseType, pluginName }, - }), - }; - } - - // License is valid and active - return { - status: LICENSE_STATUS_VALID, - }; -} diff --git a/x-pack/legacy/server/lib/check_license/check_license.test.js b/x-pack/legacy/server/lib/check_license/check_license.test.js deleted file mode 100644 index 65b599ed4a5f6..0000000000000 --- a/x-pack/legacy/server/lib/check_license/check_license.test.js +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { set } from '@elastic/safer-lodash-set'; -import { checkLicense } from './check_license'; -import { - LICENSE_STATUS_UNAVAILABLE, - LICENSE_STATUS_EXPIRED, - LICENSE_STATUS_VALID, - LICENSE_TYPE_BASIC, -} from '../../../common/constants'; - -describe('check_license', function () { - const pluginName = 'Foo'; - const minimumLicenseRequired = LICENSE_TYPE_BASIC; - let mockLicenseInfo; - beforeEach(() => (mockLicenseInfo = {})); - - describe('license information is undefined', () => { - beforeEach(() => (mockLicenseInfo = undefined)); - - it('should set status to unavailable', () => { - expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).status).toBe( - LICENSE_STATUS_UNAVAILABLE - ); - }); - - it('should set a message', () => { - expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).message).not.toBe( - undefined - ); - }); - }); - - describe('license information is not available', () => { - beforeEach(() => (mockLicenseInfo.isAvailable = () => false)); - - it('should set status to unavailable', () => { - expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).status).toBe( - LICENSE_STATUS_UNAVAILABLE - ); - }); - - it('should set a message', () => { - expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).message).not.toBe( - undefined - ); - }); - }); - - describe('license information is available', () => { - beforeEach(() => { - mockLicenseInfo.isAvailable = () => true; - set(mockLicenseInfo, 'license.getType', () => LICENSE_TYPE_BASIC); - }); - - describe('& license is trial, standard, gold, platinum', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isOneOf', () => true)); - - describe('& license is active', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true)); - - it('should set status to valid', () => { - expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).status).toBe( - LICENSE_STATUS_VALID - ); - }); - - it('should not set a message', () => { - expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).message).toBe( - undefined - ); - }); - }); - - describe('& license is expired', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false)); - - it('should set status to inactive', () => { - expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).status).toBe( - LICENSE_STATUS_EXPIRED - ); - }); - - it('should set a message', () => { - expect( - checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).message - ).not.toBe(undefined); - }); - }); - }); - - describe('& license is basic', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isOneOf', () => true)); - - describe('& license is active', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true)); - - it('should set status to valid', () => { - expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).status).toBe( - LICENSE_STATUS_VALID - ); - }); - - it('should not set a message', () => { - expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).message).toBe( - undefined - ); - }); - }); - - describe('& license is expired', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false)); - - it('should set status to inactive', () => { - expect(checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).status).toBe( - LICENSE_STATUS_EXPIRED - ); - }); - - it('should set a message', () => { - expect( - checkLicense(pluginName, minimumLicenseRequired, mockLicenseInfo).message - ).not.toBe(undefined); - }); - }); - }); - }); -}); diff --git a/x-pack/legacy/server/lib/constants/xpack_info.ts b/x-pack/legacy/server/lib/constants/xpack_info.ts deleted file mode 100644 index c58bb275245b6..0000000000000 --- a/x-pack/legacy/server/lib/constants/xpack_info.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const XPACK_INFO_API_DEFAULT_POLL_FREQUENCY_IN_MILLIS = 30001; // 30 seconds diff --git a/x-pack/legacy/server/lib/create_router/call_with_request_factory/call_with_request_factory.js b/x-pack/legacy/server/lib/create_router/call_with_request_factory/call_with_request_factory.js deleted file mode 100644 index df1ce95b31655..0000000000000 --- a/x-pack/legacy/server/lib/create_router/call_with_request_factory/call_with_request_factory.js +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const callWithRequestFactory = (server, pluginId, config) => { - const { callWithRequest } = config - ? server.plugins.elasticsearch.createCluster(pluginId, config) - : server.plugins.elasticsearch.getCluster('data'); - return callWithRequest; -}; diff --git a/x-pack/legacy/server/lib/create_router/call_with_request_factory/index.d.ts b/x-pack/legacy/server/lib/create_router/call_with_request_factory/index.d.ts deleted file mode 100644 index 3537d1bf42079..0000000000000 --- a/x-pack/legacy/server/lib/create_router/call_with_request_factory/index.d.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Legacy } from 'kibana'; -import { LegacyAPICaller } from '../../../../../../src/core/server'; - -export type CallWithRequest = (...args: any[]) => LegacyAPICaller; - -export declare function callWithRequestFactory( - server: Legacy.Server, - pluginId: string, - config?: { - plugins: any[]; - } -): CallWithRequest; diff --git a/x-pack/legacy/server/lib/create_router/error_wrappers/__tests__/wrap_custom_error.js b/x-pack/legacy/server/lib/create_router/error_wrappers/__tests__/wrap_custom_error.js deleted file mode 100644 index f9c102be7a1ff..0000000000000 --- a/x-pack/legacy/server/lib/create_router/error_wrappers/__tests__/wrap_custom_error.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { wrapCustomError } from '../wrap_custom_error'; - -describe('wrap_custom_error', () => { - describe('#wrapCustomError', () => { - it('should return a Boom object', () => { - const originalError = new Error('I am an error'); - const statusCode = 404; - const wrappedError = wrapCustomError(originalError, statusCode); - - expect(wrappedError.isBoom).to.be(true); - expect(wrappedError.output.statusCode).to.equal(statusCode); - }); - }); -}); diff --git a/x-pack/legacy/server/lib/create_router/error_wrappers/__tests__/wrap_es_error.js b/x-pack/legacy/server/lib/create_router/error_wrappers/__tests__/wrap_es_error.js deleted file mode 100644 index 8241dc4329137..0000000000000 --- a/x-pack/legacy/server/lib/create_router/error_wrappers/__tests__/wrap_es_error.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { wrapEsError } from '../wrap_es_error'; - -describe('wrap_es_error', () => { - describe('#wrapEsError', () => { - let originalError; - beforeEach(() => { - originalError = new Error('I am an error'); - originalError.statusCode = 404; - originalError.response = '{}'; - }); - - it('should return a Boom object', () => { - const wrappedError = wrapEsError(originalError); - - expect(wrappedError.isBoom).to.be(true); - }); - - it('should return the correct Boom object', () => { - const wrappedError = wrapEsError(originalError); - - expect(wrappedError.output.statusCode).to.be(originalError.statusCode); - expect(wrappedError.output.payload.message).to.be(originalError.message); - }); - - it('should return the correct Boom object with custom message', () => { - const wrappedError = wrapEsError(originalError, { 404: 'No encontrado!' }); - - expect(wrappedError.output.statusCode).to.be(originalError.statusCode); - expect(wrappedError.output.payload.message).to.be('No encontrado!'); - }); - }); -}); diff --git a/x-pack/legacy/server/lib/create_router/error_wrappers/__tests__/wrap_unknown_error.js b/x-pack/legacy/server/lib/create_router/error_wrappers/__tests__/wrap_unknown_error.js deleted file mode 100644 index 85e0b2b3033ad..0000000000000 --- a/x-pack/legacy/server/lib/create_router/error_wrappers/__tests__/wrap_unknown_error.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { wrapUnknownError } from '../wrap_unknown_error'; - -describe('wrap_unknown_error', () => { - describe('#wrapUnknownError', () => { - it('should return a Boom object', () => { - const originalError = new Error('I am an error'); - const wrappedError = wrapUnknownError(originalError); - - expect(wrappedError.isBoom).to.be(true); - }); - }); -}); diff --git a/x-pack/legacy/server/lib/create_router/error_wrappers/index.d.ts b/x-pack/legacy/server/lib/create_router/error_wrappers/index.d.ts deleted file mode 100644 index 1aaefb4e3727c..0000000000000 --- a/x-pack/legacy/server/lib/create_router/error_wrappers/index.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import Boom from 'boom'; - -export declare function wrapCustomError(error: Error, statusCode: number): Boom; - -export declare function wrapEsError(error: Error, statusCodeToMessageMap?: object): Boom; - -export declare function wrapUnknownError(error: Error): Boom; diff --git a/x-pack/legacy/server/lib/create_router/error_wrappers/index.js b/x-pack/legacy/server/lib/create_router/error_wrappers/index.js deleted file mode 100644 index f275f15637091..0000000000000 --- a/x-pack/legacy/server/lib/create_router/error_wrappers/index.js +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { wrapCustomError } from './wrap_custom_error'; -export { wrapEsError } from './wrap_es_error'; -export { wrapUnknownError } from './wrap_unknown_error'; diff --git a/x-pack/legacy/server/lib/create_router/error_wrappers/wrap_custom_error.js b/x-pack/legacy/server/lib/create_router/error_wrappers/wrap_custom_error.js deleted file mode 100644 index 3295113d38ee5..0000000000000 --- a/x-pack/legacy/server/lib/create_router/error_wrappers/wrap_custom_error.js +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Boom from 'boom'; - -/** - * Wraps a custom error into a Boom error response and returns it - * - * @param err Object error - * @param statusCode Error status code - * @return Object Boom error response - */ -export function wrapCustomError(err, statusCode) { - return Boom.boomify(err, { statusCode }); -} diff --git a/x-pack/legacy/server/lib/create_router/error_wrappers/wrap_es_error.js b/x-pack/legacy/server/lib/create_router/error_wrappers/wrap_es_error.js deleted file mode 100644 index 72be6321af8a2..0000000000000 --- a/x-pack/legacy/server/lib/create_router/error_wrappers/wrap_es_error.js +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Boom from 'boom'; - -function extractCausedByChain(causedBy = {}, accumulator = []) { - const { reason, caused_by } = causedBy; // eslint-disable-line camelcase - - if (reason) { - accumulator.push(reason); - } - - // eslint-disable-next-line camelcase - if (caused_by) { - return extractCausedByChain(caused_by, accumulator); - } - - return accumulator; -} - -/** - * Wraps an error thrown by the ES JS client into a Boom error response and returns it - * - * @param err Object Error thrown by ES JS client - * @param statusCodeToMessageMap Object Optional map of HTTP status codes => error messages - * @return Object Boom error response - */ -export function wrapEsError(err, statusCodeToMessageMap = {}) { - const { statusCode, response } = err; - - const { - error: { - root_cause = [], // eslint-disable-line camelcase - caused_by, // eslint-disable-line camelcase - } = {}, - } = JSON.parse(response); - - // If no custom message if specified for the error's status code, just - // wrap the error as a Boom error response, include the additional information from ES, and return it - if (!statusCodeToMessageMap[statusCode]) { - const boomError = Boom.boomify(err, { statusCode }); - - // The caused_by chain has the most information so use that if it's available. If not then - // settle for the root_cause. - const causedByChain = extractCausedByChain(caused_by); - const defaultCause = root_cause.length ? extractCausedByChain(root_cause[0]) : undefined; - - boomError.output.payload.cause = causedByChain.length ? causedByChain : defaultCause; - return boomError; - } - - // Otherwise, use the custom message to create a Boom error response and - // return it - const message = statusCodeToMessageMap[statusCode]; - return new Boom(message, { statusCode }); -} diff --git a/x-pack/legacy/server/lib/create_router/error_wrappers/wrap_unknown_error.js b/x-pack/legacy/server/lib/create_router/error_wrappers/wrap_unknown_error.js deleted file mode 100644 index ffd915c513362..0000000000000 --- a/x-pack/legacy/server/lib/create_router/error_wrappers/wrap_unknown_error.js +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Boom from 'boom'; - -/** - * Wraps an unknown error into a Boom error response and returns it - * - * @param err Object Unknown error - * @return Object Boom error response - */ -export function wrapUnknownError(err) { - return Boom.boomify(err); -} diff --git a/x-pack/legacy/server/lib/create_router/index.d.ts b/x-pack/legacy/server/lib/create_router/index.d.ts deleted file mode 100644 index 76e5f4b599708..0000000000000 --- a/x-pack/legacy/server/lib/create_router/index.d.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { Request, ResponseToolkit } from 'hapi'; -import { Legacy } from 'kibana'; -import { CallWithRequest } from './call_with_request_factory'; - -export * from './error_wrappers'; - -export type RouterRouteHandler = ( - req: Request, - callWithRequest: ReturnType, - responseToolkit: ResponseToolkit -) => Promise; - -export type RouterRoute = (path: string, handler: RouterRouteHandler) => Router; - -export interface Router { - get: RouterRoute; - post: RouterRoute; - put: RouterRoute; - delete: RouterRoute; - patch: RouterRoute; - isEsError: any; -} - -export declare function createRouter( - server: Legacy.Server, - pluginId: string, - apiBasePath: string, - config?: { - plugins: any[]; - } -): Router; - -export declare function isEsErrorFactory(server: Legacy.Server): any; diff --git a/x-pack/legacy/server/lib/create_router/index.js b/x-pack/legacy/server/lib/create_router/index.js deleted file mode 100644 index e4d66bdb5a48b..0000000000000 --- a/x-pack/legacy/server/lib/create_router/index.js +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import Boom from 'boom'; -import { callWithRequestFactory } from './call_with_request_factory'; -import { isEsErrorFactory as createIsEsError } from './is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from './error_wrappers'; -import { licensePreRoutingFactory } from './license_pre_routing_factory'; - -export { wrapEsError, wrapUnknownError, wrapCustomError } from './error_wrappers'; - -// Sometimes consumers will need to check if errors are ES errors, too. -export const isEsErrorFactory = (server) => { - return createIsEsError(server); -}; - -export const createRouter = (server, pluginId, apiBasePath = '', config) => { - const isEsError = isEsErrorFactory(server); - - // NOTE: The license-checking logic depends on the xpack_main plugin, so if your plugin - // consumes this helper, make sure it declares 'xpack_main' as a dependency. - const licensePreRouting = licensePreRoutingFactory(server, pluginId); - - const callWithRequestInstance = callWithRequestFactory(server, pluginId, config); - - const requestHandler = (handler) => async (request, h) => { - try { - const callWithRequest = (...args) => { - return callWithRequestInstance(request, ...args); - }; - return await handler(request, callWithRequest, h); - } catch (err) { - if (err instanceof Boom) { - throw err; - } - - if (isEsError(err)) { - throw wrapEsError(err); - } - - throw wrapUnknownError(err); - } - }; - - // Decorate base router with HTTP methods. - return ['get', 'post', 'put', 'delete', 'patch'].reduce((router, methodName) => { - router[methodName] = (subPath, handler) => { - const method = methodName.toUpperCase(); - const path = apiBasePath + subPath; - server.route({ - path, - method, - handler: requestHandler(handler), - config: { pre: [licensePreRouting] }, - }); - }; - return router; - }, {}); -}; diff --git a/x-pack/legacy/server/lib/create_router/is_es_error_factory/__tests__/is_es_error_factory.js b/x-pack/legacy/server/lib/create_router/is_es_error_factory/__tests__/is_es_error_factory.js deleted file mode 100644 index ef6fbaf9c53d0..0000000000000 --- a/x-pack/legacy/server/lib/create_router/is_es_error_factory/__tests__/is_es_error_factory.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { isEsErrorFactory } from '../is_es_error_factory'; -import { set } from '@elastic/safer-lodash-set'; - -class MockAbstractEsError {} - -describe('is_es_error_factory', () => { - let mockServer; - let isEsError; - - beforeEach(() => { - const mockEsErrors = { - _Abstract: MockAbstractEsError, - }; - mockServer = {}; - set(mockServer, 'plugins.elasticsearch.getCluster', () => ({ errors: mockEsErrors })); - - isEsError = isEsErrorFactory(mockServer); - }); - - describe('#isEsErrorFactory', () => { - it('should return a function', () => { - expect(isEsError).to.be.a(Function); - }); - - describe('returned function', () => { - it('should return true if passed-in err is a known esError', () => { - const knownEsError = new MockAbstractEsError(); - expect(isEsError(knownEsError)).to.be(true); - }); - - it('should return false if passed-in err is not a known esError', () => { - const unknownEsError = {}; - expect(isEsError(unknownEsError)).to.be(false); - }); - }); - }); -}); diff --git a/x-pack/legacy/server/lib/create_router/is_es_error_factory/is_es_error_factory.js b/x-pack/legacy/server/lib/create_router/is_es_error_factory/is_es_error_factory.js deleted file mode 100644 index 80daac5bd496d..0000000000000 --- a/x-pack/legacy/server/lib/create_router/is_es_error_factory/is_es_error_factory.js +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { memoize } from 'lodash'; - -const esErrorsFactory = memoize((server) => { - return server.plugins.elasticsearch.getCluster('admin').errors; -}); - -export function isEsErrorFactory(server) { - const esErrors = esErrorsFactory(server); - return function isEsError(err) { - return err instanceof esErrors._Abstract; - }; -} diff --git a/x-pack/legacy/server/lib/create_router/license_pre_routing_factory/__tests__/license_pre_routing_factory.js b/x-pack/legacy/server/lib/create_router/license_pre_routing_factory/__tests__/license_pre_routing_factory.js deleted file mode 100644 index dde18a0ccd7dd..0000000000000 --- a/x-pack/legacy/server/lib/create_router/license_pre_routing_factory/__tests__/license_pre_routing_factory.js +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { licensePreRoutingFactory } from '../license_pre_routing_factory'; -import { LICENSE_STATUS_INVALID, LICENSE_STATUS_VALID } from '../../../../../common/constants'; - -describe('license_pre_routing_factory', () => { - describe('#reportingFeaturePreRoutingFactory', () => { - let mockServer; - let mockLicenseCheckResults; - - beforeEach(() => { - mockServer = { - plugins: { - xpack_main: { - info: { - feature: () => ({ - getLicenseCheckResults: () => mockLicenseCheckResults, - }), - }, - }, - }, - }; - }); - - it('instantiates a new instance per plugin', () => { - const firstInstance = licensePreRoutingFactory(mockServer, 'foo'); - const secondInstance = licensePreRoutingFactory(mockServer, 'bar'); - - expect(firstInstance).to.not.be(secondInstance); - }); - - describe('status is invalid', () => { - beforeEach(() => { - mockLicenseCheckResults = { - status: LICENSE_STATUS_INVALID, - }; - }); - - it('replies with 403', () => { - const licensePreRouting = licensePreRoutingFactory(mockServer); - const stubRequest = {}; - expect(() => licensePreRouting(stubRequest)).to.throwException((response) => { - expect(response).to.be.an(Error); - expect(response.isBoom).to.be(true); - expect(response.output.statusCode).to.be(403); - }); - }); - }); - - describe('status is valid', () => { - beforeEach(() => { - mockLicenseCheckResults = { - status: LICENSE_STATUS_VALID, - }; - }); - - it('replies with nothing', () => { - const licensePreRouting = licensePreRoutingFactory(mockServer); - const stubRequest = {}; - const response = licensePreRouting(stubRequest); - expect(response).to.be(null); - }); - }); - }); -}); diff --git a/x-pack/legacy/server/lib/create_router/license_pre_routing_factory/index.js b/x-pack/legacy/server/lib/create_router/license_pre_routing_factory/index.js deleted file mode 100644 index 0743e443955f4..0000000000000 --- a/x-pack/legacy/server/lib/create_router/license_pre_routing_factory/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { licensePreRoutingFactory } from './license_pre_routing_factory'; diff --git a/x-pack/legacy/server/lib/create_router/license_pre_routing_factory/license_pre_routing_factory.js b/x-pack/legacy/server/lib/create_router/license_pre_routing_factory/license_pre_routing_factory.js deleted file mode 100644 index 81640ebb35ea9..0000000000000 --- a/x-pack/legacy/server/lib/create_router/license_pre_routing_factory/license_pre_routing_factory.js +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { wrapCustomError } from '../error_wrappers'; -import { LICENSE_STATUS_VALID } from '../../../../common/constants'; - -export const licensePreRoutingFactory = (server, pluginId) => { - return () => { - const xpackMainPlugin = server.plugins.xpack_main; - const licenseCheckResults = xpackMainPlugin.info.feature(pluginId).getLicenseCheckResults(); - - // Apps which don't have any license restrictions will return undefined license check results. - if (licenseCheckResults) { - if (licenseCheckResults.status !== LICENSE_STATUS_VALID) { - const error = new Error(licenseCheckResults.message); - const statusCode = 403; - throw wrapCustomError(error, statusCode); - } - } - - return null; - }; -}; diff --git a/x-pack/legacy/server/lib/key_case_converter.js b/x-pack/legacy/server/lib/key_case_converter.js deleted file mode 100644 index a2a5452b3a1d9..0000000000000 --- a/x-pack/legacy/server/lib/key_case_converter.js +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import _ from 'lodash'; - -// Note: This function uses _.clone. This will clone objects created by constructors other than Object -// to plain Object objects. Uncloneable values such as functions, DOM nodes, Maps, Sets, and WeakMaps -// will be cloned to the empty object. -function convertKeysToSpecifiedCaseDeep(object, caseConversionFunction) { - // Base case - if (!(_.isPlainObject(object) || _.isArray(object))) { - return object; - } - - // Clone (so we don't modify the original object that was passed in) - let newObject; - if (Array.isArray(object)) { - newObject = object.slice(0); - } else { - newObject = _.clone(object); - - // Convert top-level keys - newObject = _.mapKeys(newObject, (value, key) => caseConversionFunction(key)); - } - - // Recursively convert nested object keys - _.forEach( - newObject, - (value, key) => (newObject[key] = convertKeysToSpecifiedCaseDeep(value, caseConversionFunction)) - ); - - return newObject; -} - -function validateObject(object) { - if (!(_.isPlainObject(object) || _.isArray(object))) { - throw new Error('Specified object should be an Object or Array'); - } -} - -export function convertKeysToSnakeCaseDeep(object) { - validateObject(object); - return convertKeysToSpecifiedCaseDeep(object, _.snakeCase); -} - -export function convertKeysToCamelCaseDeep(object) { - validateObject(object); - return convertKeysToSpecifiedCaseDeep(object, _.camelCase); -} diff --git a/x-pack/legacy/server/lib/parse_kibana_state.js b/x-pack/legacy/server/lib/parse_kibana_state.js deleted file mode 100644 index a6c9bfbb511c1..0000000000000 --- a/x-pack/legacy/server/lib/parse_kibana_state.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { set } from '@elastic/safer-lodash-set'; -import { isPlainObject, omit, get } from 'lodash'; -import rison from 'rison-node'; - -const stateTypeKeys = { - global: '_g', - app: '_a', -}; - -class KibanaState { - constructor(query, type = 'global') { - const propId = stateTypeKeys[type]; - if (!isPlainObject(query)) throw new TypeError('Query parameter must be an object'); - if (!propId) throw new TypeError(`Unknown state type: '${type}'`); - - const queryValue = query[propId]; - - this.exists = Boolean(queryValue); - this.state = queryValue ? rison.decode(queryValue) : {}; - this.type = type; - } - - removeProps(props) { - this.state = omit(this.state, props); - } - - get(prop, defVal) { - return get(this.state, prop, defVal); - } - - set(prop, val) { - return set(this.state, prop, val); - } - - toString() { - return rison.encode(this.state); - } - - toQuery() { - const index = stateTypeKeys[this.type]; - const output = {}; - output[index] = this.toString(); - return output; - } -} - -export function parseKibanaState(query, type) { - return new KibanaState(query, type); -} diff --git a/x-pack/legacy/server/lib/register_license_checker/index.d.ts b/x-pack/legacy/server/lib/register_license_checker/index.d.ts deleted file mode 100644 index 555008921df42..0000000000000 --- a/x-pack/legacy/server/lib/register_license_checker/index.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Legacy } from 'kibana'; -import { LicenseType } from '../../../common/constants'; - -export declare function registerLicenseChecker( - server: Legacy.Server, - pluginId: string, - pluginName: string, - minimumLicenseRequired: LicenseType -): void; diff --git a/x-pack/legacy/server/lib/register_license_checker/index.js b/x-pack/legacy/server/lib/register_license_checker/index.js deleted file mode 100644 index 7b0f97c38d129..0000000000000 --- a/x-pack/legacy/server/lib/register_license_checker/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { registerLicenseChecker } from './register_license_checker'; diff --git a/x-pack/legacy/server/lib/register_license_checker/register_license_checker.js b/x-pack/legacy/server/lib/register_license_checker/register_license_checker.js deleted file mode 100644 index 57cbe30c25cb2..0000000000000 --- a/x-pack/legacy/server/lib/register_license_checker/register_license_checker.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { pairwise } from 'rxjs/operators'; - -import { ServiceStatusLevels } from '../../../../../src/core/server'; -import { checkLicense } from '../check_license'; - -export function registerLicenseChecker(server, pluginId, pluginName, minimumLicenseRequired) { - const xpackMainPlugin = server.plugins.xpack_main; - const subscription = server.newPlatform.setup.core.status.core$ - .pipe(pairwise()) - .subscribe(([coreLast, coreCurrent]) => { - if ( - !subscription.closed && - coreLast.elasticsearch.level !== ServiceStatusLevels.available && - coreCurrent.elasticsearch.level === ServiceStatusLevels.available - ) { - // Unsubscribe as soon as ES becomes available so this function only runs once - subscription.unsubscribe(); - - // Register a function that is called whenever the xpack info changes, - // to re-compute the license check results for this plugin - xpackMainPlugin.info - .feature(pluginId) - .registerLicenseCheckResultsGenerator((xpackLicenseInfo) => { - return checkLicense(pluginName, minimumLicenseRequired, xpackLicenseInfo); - }); - } - }); -} diff --git a/x-pack/legacy/server/lib/watch_status_and_license_to_initialize.js b/x-pack/legacy/server/lib/watch_status_and_license_to_initialize.js deleted file mode 100644 index 109dbbb20e35d..0000000000000 --- a/x-pack/legacy/server/lib/watch_status_and_license_to_initialize.js +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import * as Rx from 'rxjs'; -import { catchError, mergeMap, map, switchMap, tap } from 'rxjs/operators'; - -export const RETRY_SCALE_DURATION = 100; -export const RETRY_DURATION_MAX = 10000; - -const calculateDuration = (i) => { - const duration = i * RETRY_SCALE_DURATION; - if (duration > RETRY_DURATION_MAX) { - return RETRY_DURATION_MAX; - } - - return duration; -}; - -// we can't use a retryWhen here, because we want to propagate the red status and then retry -const propagateRedStatusAndScaleRetry = () => { - let i = 0; - return (err, caught) => - Rx.concat( - Rx.of({ - state: 'red', - message: err.message, - }), - Rx.timer(calculateDuration(++i)).pipe(mergeMap(() => caught)) - ); -}; - -export function watchStatusAndLicenseToInitialize(xpackMainPlugin, downstreamPlugin, initialize) { - const xpackInfo = xpackMainPlugin.info; - const xpackInfoFeature = xpackInfo.feature(downstreamPlugin.id); - - const upstreamStatus = xpackMainPlugin.status; - const currentStatus$ = Rx.of({ - state: upstreamStatus.state, - message: upstreamStatus.message, - }); - const newStatus$ = Rx.fromEvent( - upstreamStatus, - 'change', - null, - (previousState, previousMsg, state, message) => { - return { - state, - message, - }; - } - ); - const status$ = Rx.merge(currentStatus$, newStatus$); - - const currentLicense$ = Rx.of(xpackInfoFeature.getLicenseCheckResults()); - const newLicense$ = Rx.fromEventPattern(xpackInfo.onLicenseInfoChange.bind(xpackInfo)).pipe( - map(() => xpackInfoFeature.getLicenseCheckResults()) - ); - const license$ = Rx.merge(currentLicense$, newLicense$); - - Rx.combineLatest(status$, license$) - .pipe( - map(([status, license]) => ({ status, license })), - switchMap(({ status, license }) => { - if (status.state !== 'green') { - return Rx.of({ state: status.state, message: status.message }); - } - - return Rx.defer(() => initialize(license)).pipe( - map(() => ({ - state: 'green', - message: 'Ready', - })), - catchError(propagateRedStatusAndScaleRetry()) - ); - }), - tap(({ state, message }) => { - downstreamPlugin.status[state](message); - }) - ) - .subscribe(); -} diff --git a/x-pack/legacy/server/lib/watch_status_and_license_to_initialize.test.js b/x-pack/legacy/server/lib/watch_status_and_license_to_initialize.test.js deleted file mode 100644 index 33282b7591db7..0000000000000 --- a/x-pack/legacy/server/lib/watch_status_and_license_to_initialize.test.js +++ /dev/null @@ -1,301 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { EventEmitter } from 'events'; -import { - watchStatusAndLicenseToInitialize, - RETRY_SCALE_DURATION, - RETRY_DURATION_MAX, -} from './watch_status_and_license_to_initialize'; - -const createMockXpackMainPluginAndFeature = (featureId) => { - const licenseChangeCallbacks = []; - - const mockFeature = { - getLicenseCheckResults: jest.fn(), - mock: { - triggerLicenseChange: () => { - for (const callback of licenseChangeCallbacks) { - callback(); - } - }, - setLicenseCheckResults: (value) => { - mockFeature.getLicenseCheckResults.mockReturnValue(value); - }, - }, - }; - - const mockXpackMainPlugin = { - info: { - onLicenseInfoChange: (callback) => { - licenseChangeCallbacks.push(callback); - }, - feature: (id) => { - if (id === featureId) { - return mockFeature; - } - throw new Error('Unexpected feature'); - }, - }, - status: new EventEmitter(), - mock: { - setStatus: (state, message) => { - mockXpackMainPlugin.status.state = state; - mockXpackMainPlugin.status.message = message; - mockXpackMainPlugin.status.emit('change', null, null, state, message); - }, - }, - }; - - return { mockXpackMainPlugin, mockFeature }; -}; - -const createMockDownstreamPlugin = (id) => { - const defaultImplementation = () => { - throw new Error('Not implemented'); - }; - return { - id, - status: { - disabled: jest.fn().mockImplementation(defaultImplementation), - yellow: jest.fn().mockImplementation(defaultImplementation), - green: jest.fn().mockImplementation(defaultImplementation), - red: jest.fn().mockImplementation(defaultImplementation), - }, - }; -}; - -const advanceRetry = async (initializeCount) => { - await Promise.resolve(); - let duration = initializeCount * RETRY_SCALE_DURATION; - if (duration > RETRY_DURATION_MAX) { - duration = RETRY_DURATION_MAX; - } - jest.advanceTimersByTime(duration); -}; - -['red', 'yellow', 'disabled'].forEach((state) => { - test(`mirrors ${state} immediately`, () => { - const pluginId = 'foo-plugin'; - const message = `${state} is now the state`; - const { mockXpackMainPlugin } = createMockXpackMainPluginAndFeature(pluginId); - mockXpackMainPlugin.mock.setStatus(state, message); - const downstreamPlugin = createMockDownstreamPlugin(pluginId); - const initializeMock = jest.fn(); - downstreamPlugin.status[state].mockImplementation(() => {}); - - watchStatusAndLicenseToInitialize(mockXpackMainPlugin, downstreamPlugin, initializeMock); - - expect(initializeMock).not.toHaveBeenCalled(); - expect(downstreamPlugin.status[state]).toHaveBeenCalledTimes(1); - expect(downstreamPlugin.status[state]).toHaveBeenCalledWith(message); - }); -}); - -test(`calls initialize and doesn't immediately set downstream status when the initial status is green`, () => { - const pluginId = 'foo-plugin'; - const { mockXpackMainPlugin, mockFeature } = createMockXpackMainPluginAndFeature(pluginId); - mockXpackMainPlugin.mock.setStatus('green', 'green is now the state'); - const licenseCheckResults = Symbol(); - mockFeature.mock.setLicenseCheckResults(licenseCheckResults); - const downstreamPlugin = createMockDownstreamPlugin(pluginId); - const initializeMock = jest.fn().mockImplementation(() => new Promise(() => {})); - - watchStatusAndLicenseToInitialize(mockXpackMainPlugin, downstreamPlugin, initializeMock); - - expect(initializeMock).toHaveBeenCalledTimes(1); - expect(initializeMock).toHaveBeenCalledWith(licenseCheckResults); - expect(downstreamPlugin.status.green).toHaveBeenCalledTimes(0); -}); - -test(`sets downstream plugin's status to green when initialize resolves`, (done) => { - const pluginId = 'foo-plugin'; - const { mockXpackMainPlugin, mockFeature } = createMockXpackMainPluginAndFeature(pluginId); - mockXpackMainPlugin.mock.setStatus('green', 'green is now the state'); - const licenseCheckResults = Symbol(); - mockFeature.mock.setLicenseCheckResults(licenseCheckResults); - const downstreamPlugin = createMockDownstreamPlugin(pluginId); - const initializeMock = jest.fn().mockImplementation(() => Promise.resolve()); - - watchStatusAndLicenseToInitialize(mockXpackMainPlugin, downstreamPlugin, initializeMock); - - expect(initializeMock).toHaveBeenCalledTimes(1); - expect(initializeMock).toHaveBeenCalledWith(licenseCheckResults); - downstreamPlugin.status.green.mockImplementation((actualMessage) => { - expect(actualMessage).toBe('Ready'); - done(); - }); -}); - -test(`sets downstream plugin's status to red when initialize initially rejects, and continually polls initialize`, (done) => { - jest.useFakeTimers(); - - const pluginId = 'foo-plugin'; - const errorMessage = 'the error message'; - const { mockXpackMainPlugin, mockFeature } = createMockXpackMainPluginAndFeature(pluginId); - mockXpackMainPlugin.mock.setStatus('green'); - const licenseCheckResults = Symbol(); - mockFeature.mock.setLicenseCheckResults(licenseCheckResults); - const downstreamPlugin = createMockDownstreamPlugin(pluginId); - - let isRed = false; - let initializeCount = 0; - const initializeMock = jest.fn().mockImplementation(() => { - ++initializeCount; - - // on the second retry, ensure we already set the status to red - if (initializeCount === 2) { - expect(isRed).toBe(true); - } - - // this should theoretically continue indefinitely, but we only have so long to run the tests - if (initializeCount === 100) { - done(); - } - - // everytime this is called, we have to wait for a new promise to be resolved - // allowing the Promise the we return below to run, and then advance the timers - setImmediate(() => { - advanceRetry(initializeCount); - }); - return Promise.reject(new Error(errorMessage)); - }); - - watchStatusAndLicenseToInitialize(mockXpackMainPlugin, downstreamPlugin, initializeMock); - - expect(initializeMock).toHaveBeenCalledTimes(1); - expect(initializeMock).toHaveBeenCalledWith(licenseCheckResults); - downstreamPlugin.status.red.mockImplementation((message) => { - isRed = true; - expect(message).toBe(errorMessage); - }); -}); - -test(`sets downstream plugin's status to green when initialize resolves after rejecting 10 times`, (done) => { - jest.useFakeTimers(); - - const pluginId = 'foo-plugin'; - const errorMessage = 'the error message'; - const { mockXpackMainPlugin, mockFeature } = createMockXpackMainPluginAndFeature(pluginId); - mockXpackMainPlugin.mock.setStatus('green'); - const licenseCheckResults = Symbol(); - mockFeature.mock.setLicenseCheckResults(licenseCheckResults); - const downstreamPlugin = createMockDownstreamPlugin(pluginId); - - let initializeCount = 0; - const initializeMock = jest.fn().mockImplementation(() => { - ++initializeCount; - - // everytime this is called, we have to wait for a new promise to be resolved - // allowing the Promise the we return below to run, and then advance the timers - setImmediate(() => { - advanceRetry(initializeCount); - }); - - if (initializeCount >= 10) { - return Promise.resolve(); - } - - return Promise.reject(new Error(errorMessage)); - }); - - watchStatusAndLicenseToInitialize(mockXpackMainPlugin, downstreamPlugin, initializeMock); - - expect(initializeMock).toHaveBeenCalledTimes(1); - expect(initializeMock).toHaveBeenCalledWith(licenseCheckResults); - downstreamPlugin.status.red.mockImplementation((message) => { - expect(initializeCount).toBeLessThan(10); - expect(message).toBe(errorMessage); - }); - downstreamPlugin.status.green.mockImplementation((message) => { - expect(initializeCount).toBe(10); - expect(message).toBe('Ready'); - done(); - }); -}); - -test(`calls initialize twice when it gets a new license and the status is green`, (done) => { - const pluginId = 'foo-plugin'; - const { mockXpackMainPlugin, mockFeature } = createMockXpackMainPluginAndFeature(pluginId); - mockXpackMainPlugin.mock.setStatus('green'); - const firstLicenseCheckResults = Symbol(); - const secondLicenseCheckResults = Symbol(); - mockFeature.mock.setLicenseCheckResults(firstLicenseCheckResults); - const downstreamPlugin = createMockDownstreamPlugin(pluginId); - const initializeMock = jest.fn().mockImplementation(() => Promise.resolve()); - - let count = 0; - downstreamPlugin.status.green.mockImplementation((message) => { - expect(message).toBe('Ready'); - ++count; - if (count === 1) { - mockFeature.mock.setLicenseCheckResults(secondLicenseCheckResults); - mockFeature.mock.triggerLicenseChange(); - } - if (count === 2) { - expect(initializeMock).toHaveBeenCalledWith(firstLicenseCheckResults); - expect(initializeMock).toHaveBeenCalledWith(secondLicenseCheckResults); - expect(initializeMock).toHaveBeenCalledTimes(2); - done(); - } - }); - - watchStatusAndLicenseToInitialize(mockXpackMainPlugin, downstreamPlugin, initializeMock); -}); - -test(`doesn't call initialize twice when it gets a new license when the status isn't green`, (done) => { - const pluginId = 'foo-plugin'; - const redMessage = 'the red message'; - const { mockXpackMainPlugin, mockFeature } = createMockXpackMainPluginAndFeature(pluginId); - mockXpackMainPlugin.mock.setStatus('green'); - const firstLicenseCheckResults = Symbol(); - const secondLicenseCheckResults = Symbol(); - mockFeature.mock.setLicenseCheckResults(firstLicenseCheckResults); - const downstreamPlugin = createMockDownstreamPlugin(pluginId); - const initializeMock = jest.fn().mockImplementation(() => Promise.resolve()); - - downstreamPlugin.status.green.mockImplementation((message) => { - expect(message).toBe('Ready'); - mockXpackMainPlugin.mock.setStatus('red', redMessage); - mockFeature.mock.setLicenseCheckResults(secondLicenseCheckResults); - mockFeature.mock.triggerLicenseChange(); - }); - - downstreamPlugin.status.red.mockImplementation((message) => { - expect(message).toBe(redMessage); - expect(initializeMock).toHaveBeenCalledTimes(1); - expect(initializeMock).toHaveBeenCalledWith(firstLicenseCheckResults); - done(); - }); - - watchStatusAndLicenseToInitialize(mockXpackMainPlugin, downstreamPlugin, initializeMock); -}); - -test(`calls initialize twice when the status changes to green twice`, (done) => { - const pluginId = 'foo-plugin'; - const { mockXpackMainPlugin, mockFeature } = createMockXpackMainPluginAndFeature(pluginId); - mockXpackMainPlugin.mock.setStatus('green'); - const licenseCheckResults = Symbol(); - mockFeature.mock.setLicenseCheckResults(licenseCheckResults); - const downstreamPlugin = createMockDownstreamPlugin(pluginId); - const initializeMock = jest.fn().mockImplementation(() => Promise.resolve()); - - let count = 0; - downstreamPlugin.status.green.mockImplementation((message) => { - expect(message).toBe('Ready'); - ++count; - if (count === 1) { - mockXpackMainPlugin.mock.setStatus('green'); - } - if (count === 2) { - expect(initializeMock).toHaveBeenCalledWith(licenseCheckResults); - expect(initializeMock).toHaveBeenCalledTimes(2); - done(); - } - }); - - watchStatusAndLicenseToInitialize(mockXpackMainPlugin, downstreamPlugin, initializeMock); -}); diff --git a/x-pack/legacy/server/lib/xpack_usage.js b/x-pack/legacy/server/lib/xpack_usage.js deleted file mode 100644 index 50b50ba18c37f..0000000000000 --- a/x-pack/legacy/server/lib/xpack_usage.js +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export function xpackUsage(client) { - /* - * Get an object over the Usage API that as available/enabled data and some - * select metadata for each of the X-Pack UI plugins - */ - return client.transport.request({ - method: 'GET', - path: '/_xpack/usage', - }); -} diff --git a/x-pack/package.json b/x-pack/package.json index 0d9727e8e7caa..61d257cdaadc6 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -30,7 +30,7 @@ }, "devDependencies": { "@cypress/webpack-preprocessor": "^4.1.0", - "@elastic/apm-rum-react": "^1.2.3", + "@elastic/apm-rum-react": "^1.2.4", "@elastic/maki": "6.3.0", "@kbn/dev-utils": "1.0.0", "@kbn/es": "1.0.0", @@ -111,7 +111,7 @@ "@types/proper-lockfile": "^3.0.1", "@types/puppeteer": "^1.20.1", "@types/react": "^16.9.36", - "@types/react-beautiful-dnd": "^12.1.1", + "@types/react-beautiful-dnd": "^13.0.0", "@types/react-dom": "^16.9.8", "@types/react-redux": "^7.1.9", "@types/react-router-dom": "^5.1.5", @@ -221,7 +221,7 @@ "proxyquire": "1.8.0", "re-resizable": "^6.1.1", "react-apollo": "^2.1.4", - "react-beautiful-dnd": "^12.2.0", + "react-beautiful-dnd": "^13.0.0", "react-docgen-typescript-loader": "^3.1.1", "react-dropzone": "^4.2.9", "react-fast-compare": "^2.0.4", diff --git a/x-pack/plugins/actions/common/types.ts b/x-pack/plugins/actions/common/types.ts index 49e8f3e80b14a..41ec4d2a88e9f 100644 --- a/x-pack/plugins/actions/common/types.ts +++ b/x-pack/plugins/actions/common/types.ts @@ -24,3 +24,13 @@ export interface ActionResult { config: Record; isPreconfigured: boolean; } + +// the result returned from an action type executor function +export interface ActionTypeExecutorResult { + actionId: string; + status: 'ok' | 'error'; + message?: string; + serviceMessage?: string; + data?: Data; + retry?: null | boolean | Date; +} diff --git a/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts b/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts index 7a0e24521a1c6..3d92d5ebf33fc 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts @@ -284,4 +284,47 @@ describe('execute()', () => { ] `); }); + + test('resolves with an error when an error occurs in the indexing operation', async () => { + const secrets = {}; + // minimal params + const config = { index: 'index-value', refresh: false, executionTimeField: null }; + const params = { + documents: [{ '': 'bob' }], + }; + + const actionId = 'some-id'; + + services.callCluster.mockResolvedValue({ + took: 0, + errors: true, + items: [ + { + index: { + _index: 'indexme', + _id: '7buTjHQB0SuNSiS9Hayt', + status: 400, + error: { + type: 'mapper_parsing_exception', + reason: 'failed to parse', + caused_by: { + type: 'illegal_argument_exception', + reason: 'field name cannot be an empty string', + }, + }, + }, + }, + ], + }); + + expect(await actionType.executor({ actionId, config, secrets, params, services })) + .toMatchInlineSnapshot(` + Object { + "actionId": "some-id", + "message": "error indexing documents", + "serviceMessage": "failed to parse (field name cannot be an empty string)", + "status": "error", + } + `); + }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/es_index.ts b/x-pack/plugins/actions/server/builtin_action_types/es_index.ts index 53bf75651b1e5..868c07b775c78 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/es_index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/es_index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { curry } from 'lodash'; +import { curry, find } from 'lodash'; import { i18n } from '@kbn/i18n'; import { schema, TypeOf } from '@kbn/config-schema'; @@ -85,21 +85,39 @@ async function executor( refresh: config.refresh, }; - let result; try { - result = await services.callCluster('bulk', bulkParams); + const result = await services.callCluster('bulk', bulkParams); + + const err = find(result.items, 'index.error.reason'); + if (err) { + return wrapErr( + `${err.index.error!.reason}${ + err.index.error?.caused_by ? ` (${err.index.error?.caused_by?.reason})` : '' + }`, + actionId, + logger + ); + } + + return { status: 'ok', data: result, actionId }; } catch (err) { - const message = i18n.translate('xpack.actions.builtin.esIndex.errorIndexingErrorMessage', { - defaultMessage: 'error indexing documents', - }); - logger.error(`error indexing documents: ${err.message}`); - return { - status: 'error', - actionId, - message, - serviceMessage: err.message, - }; + return wrapErr(err.message, actionId, logger); } +} - return { status: 'ok', data: result, actionId }; +function wrapErr( + errMessage: string, + actionId: string, + logger: Logger +): ActionTypeExecutorResult { + const message = i18n.translate('xpack.actions.builtin.esIndex.errorIndexingErrorMessage', { + defaultMessage: 'error indexing documents', + }); + logger.error(`error indexing documents: ${errMessage}`); + return { + status: 'error', + actionId, + message, + serviceMessage: errMessage, + }; } diff --git a/x-pack/plugins/actions/server/constants/plugin.ts b/x-pack/plugins/actions/server/constants/plugin.ts index 7d20eb6990247..b82464bd92a18 100644 --- a/x-pack/plugins/actions/server/constants/plugin.ts +++ b/x-pack/plugins/actions/server/constants/plugin.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LICENSE_TYPE_BASIC, LicenseType } from '../../../../legacy/common/constants'; +import { LicenseType } from '../../../licensing/server'; export const PLUGIN = { ID: 'actions', - MINIMUM_LICENSE_REQUIRED: LICENSE_TYPE_BASIC as LicenseType, // TODO: supposed to be changed up on requirements + MINIMUM_LICENSE_REQUIRED: 'basic' as LicenseType, // TODO: supposed to be changed up on requirements // eslint-disable-next-line @typescript-eslint/no-explicit-any getI18nName: (i18n: any): string => i18n.translate('xpack.actions.appName', { diff --git a/x-pack/plugins/actions/server/types.ts b/x-pack/plugins/actions/server/types.ts index 3e92ca331bb93..a23a2b0893261 100644 --- a/x-pack/plugins/actions/server/types.ts +++ b/x-pack/plugins/actions/server/types.ts @@ -15,6 +15,8 @@ import { SavedObjectsClientContract, SavedObjectAttributes, } from '../../../../src/core/server'; +import { ActionTypeExecutorResult } from '../common'; +export { ActionTypeExecutorResult } from '../common'; export type WithoutQueryAndParams = Pick>; export type GetServicesFunction = (request: KibanaRequest) => Services; @@ -80,16 +82,6 @@ export interface FindActionResult extends ActionResult { referencedByCount: number; } -// the result returned from an action type executor function -export interface ActionTypeExecutorResult { - actionId: string; - status: 'ok' | 'error'; - message?: string; - serviceMessage?: string; - data?: Data; - retry?: null | boolean | Date; -} - // signature of the action type executor function export type ExecutorType = ( options: ActionTypeExecutorOptions diff --git a/x-pack/plugins/alerts/server/constants/plugin.ts b/x-pack/plugins/alerts/server/constants/plugin.ts index c180b68680841..4e1e0c59e0b48 100644 --- a/x-pack/plugins/alerts/server/constants/plugin.ts +++ b/x-pack/plugins/alerts/server/constants/plugin.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LICENSE_TYPE_BASIC, LicenseType } from '../../../../legacy/common/constants'; +import { LicenseType } from '../../../licensing/server'; export const PLUGIN = { ID: 'alerts', - MINIMUM_LICENSE_REQUIRED: LICENSE_TYPE_BASIC as LicenseType, // TODO: supposed to be changed up on requirements + MINIMUM_LICENSE_REQUIRED: 'basic' as LicenseType, // TODO: supposed to be changed up on requirements // all plugins seem to use getI18nName with any // eslint-disable-next-line @typescript-eslint/no-explicit-any getI18nName: (i18n: any): string => diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.ts index 5be684eca4651..7ea3f83d747c0 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pickBy, mapValues, omit, without } from 'lodash'; +import { pickBy, mapValues, without } from 'lodash'; import { Logger, KibanaRequest } from '../../../../../src/core/server'; import { TaskRunnerContext } from './task_runner_factory'; import { ConcreteTaskInstance } from '../../../task_manager/server'; @@ -228,12 +228,13 @@ export class TaskRunner { }); if (!muteAll) { - const enabledAlertInstances = omit(instancesWithScheduledActions, ...mutedInstanceIds); + const mutedInstanceIdsSet = new Set(mutedInstanceIds); await Promise.all( - Object.entries(enabledAlertInstances) + Object.entries(instancesWithScheduledActions) .filter( - ([, alertInstance]: [string, AlertInstance]) => !alertInstance.isThrottled(throttle) + ([alertInstanceName, alertInstance]: [string, AlertInstance]) => + !alertInstance.isThrottled(throttle) && !mutedInstanceIdsSet.has(alertInstanceName) ) .map(([id, alertInstance]: [string, AlertInstance]) => this.executeAlertInstance(id, alertInstance, executionHandler) diff --git a/x-pack/plugins/apm/common/service_health_status.ts b/x-pack/plugins/apm/common/service_health_status.ts index 468f06ab97af8..1d4bcfb3b0e07 100644 --- a/x-pack/plugins/apm/common/service_health_status.ts +++ b/x-pack/plugins/apm/common/service_health_status.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { ANOMALY_SEVERITY } from '../../ml/common'; -import { EuiTheme } from '../../../legacy/common/eui_styled_components'; +import { EuiTheme } from '../../xpack_legacy/common'; export enum ServiceHealthStatus { healthy = 'healthy', diff --git a/x-pack/plugins/apm/e2e/cypress/integration/csm_dashboard.feature b/x-pack/plugins/apm/e2e/cypress/integration/csm_dashboard.feature index ac4188a598458..7b894b6ca7aac 100644 --- a/x-pack/plugins/apm/e2e/cypress/integration/csm_dashboard.feature +++ b/x-pack/plugins/apm/e2e/cypress/integration/csm_dashboard.feature @@ -27,3 +27,11 @@ Feature: CSM Dashboard Given a user clicks the page load breakdown filter When the user selected the breakdown Then breakdown series should appear in chart + + Scenario: Search by url filter focus + When a user clicks inside url search field + Then it displays top pages in the suggestion popover + + Scenario: Search by url filter + When a user enters a query in url search field + Then it should filter results based on query diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/url_search_filter.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/url_search_filter.ts new file mode 100644 index 0000000000000..3b5dd70065055 --- /dev/null +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/url_search_filter.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { When, Then } from 'cypress-cucumber-preprocessor/steps'; +import { DEFAULT_TIMEOUT } from './csm_dashboard'; + +When(`a user clicks inside url search field`, () => { + // wait for all loading to finish + cy.get('kbnLoadingIndicator').should('not.be.visible'); + cy.get('.euiStat__title-isLoading').should('not.be.visible'); + cy.get('span[data-cy=csmUrlFilter]', DEFAULT_TIMEOUT).within(() => { + cy.get('input.euiFieldSearch').click(); + }); +}); + +Then(`it displays top pages in the suggestion popover`, () => { + cy.get('kbnLoadingIndicator').should('not.be.visible'); + + cy.get('div.euiPopover__panel-isOpen', DEFAULT_TIMEOUT).within(() => { + const listOfUrls = cy.get('li.euiSelectableListItem'); + listOfUrls.should('have.length', 5); + + const actualUrlsText = [ + 'http://opbeans-node:3000/dashboardPage views: 17Page load duration: 109 ms ', + 'http://opbeans-node:3000/ordersPage views: 14Page load duration: 72 ms', + ]; + + cy.get('li.euiSelectableListItem') + .eq(0) + .should('have.text', actualUrlsText[0]); + cy.get('li.euiSelectableListItem') + .eq(1) + .should('have.text', actualUrlsText[1]); + }); +}); + +When(`a user enters a query in url search field`, () => { + cy.get('kbnLoadingIndicator').should('not.be.visible'); + + cy.get('[data-cy=csmUrlFilter]').within(() => { + cy.get('input.euiSelectableSearch').type('cus'); + }); + + cy.get('kbnLoadingIndicator').should('not.be.visible'); +}); + +Then(`it should filter results based on query`, () => { + cy.get('kbnLoadingIndicator').should('not.be.visible'); + + cy.get('div.euiPopover__panel-isOpen', DEFAULT_TIMEOUT).within(() => { + const listOfUrls = cy.get('li.euiSelectableListItem'); + listOfUrls.should('have.length', 1); + + const actualUrlsText = [ + 'http://opbeans-node:3000/customersPage views: 10Page load duration: 76 ms ', + ]; + + cy.get('li.euiSelectableListItem') + .eq(0) + .should('have.text', actualUrlsText[0]); + }); +}); diff --git a/x-pack/plugins/apm/e2e/ingest-data/replay.js b/x-pack/plugins/apm/e2e/ingest-data/replay.js index 6bab95635f558..74c86b1b09ab4 100644 --- a/x-pack/plugins/apm/e2e/ingest-data/replay.js +++ b/x-pack/plugins/apm/e2e/ingest-data/replay.js @@ -70,34 +70,40 @@ function incrementSpinnerCount({ success }) { } let iterIndex = 0; +function setItemMetaAndHeaders(item) { + const headers = { + 'content-type': 'application/x-ndjson', + }; + + if (SECRET_TOKEN) { + headers.Authorization = `Bearer ${SECRET_TOKEN}`; + } + + if (item.url === '/intake/v2/rum/events') { + if (iterIndex === userAgents.length) { + // set some event agent to opbean + setRumAgent(item); + iterIndex = 0; + } + headers['User-Agent'] = userAgents[iterIndex]; + headers['X-Forwarded-For'] = userIps[iterIndex]; + iterIndex++; + } + return headers; +} + function setRumAgent(item) { - item.body = item.body.replace( - '"name":"client"', - '"name":"opbean-client-rum"' - ); + if (item.body) { + item.body = item.body.replace( + '"name":"client"', + '"name":"opbean-client-rum"' + ); + } } -async function insertItem(item) { +async function insertItem(item, headers) { try { const url = `${APM_SERVER_URL}${item.url}`; - const headers = { - 'content-type': 'application/x-ndjson', - }; - - if (item.url === '/intake/v2/rum/events') { - if (iterIndex === userAgents.length) { - // set some event agent to opbean - setRumAgent(item); - iterIndex = 0; - } - headers['User-Agent'] = userAgents[iterIndex]; - headers['X-Forwarded-For'] = userIps[iterIndex]; - iterIndex++; - } - - if (SECRET_TOKEN) { - headers.Authorization = `Bearer ${SECRET_TOKEN}`; - } await axios({ method: item.method, @@ -133,8 +139,9 @@ async function init() { await Promise.all( items.map(async (item) => { try { + const headers = setItemMetaAndHeaders(item); // retry 5 times with exponential backoff - await pRetry(() => limit(() => insertItem(item)), { + await pRetry(() => limit(() => insertItem(item, headers)), { retries: 5, }); incrementSpinnerCount({ success: true }); diff --git a/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx b/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx index 4aa2d841e8deb..0d61ca8e39845 100644 --- a/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx @@ -62,15 +62,6 @@ export function renderAsRedirectTo(to: string) { // If you provide an inline function to the component prop, you would create a // new component every render. This results in the existing component unmounting // and the new component mounting instead of just updating the existing component. -// -// This means you should use `render` if you're providing an inline function. -// However, the `ApmRoute` component from @elastic/apm-rum-react, only supports -// `component`, and will give you a large console warning if you use `render`. -// -// This warning cannot be turned off -// (see https://github.com/elastic/apm-agent-rum-js/issues/881) so while this is -// slightly more code, it provides better performance without causing console -// warnings to appear. function HomeServices() { return ; } @@ -153,7 +144,7 @@ export const routes: APMRouteDefinition[] = [ { exact: true, path: '/', - component: renderAsRedirectTo('/services'), + render: renderAsRedirectTo('/services'), breadcrumb: 'APM', }, { @@ -175,7 +166,7 @@ export const routes: APMRouteDefinition[] = [ { exact: true, path: '/settings', - component: renderAsRedirectTo('/settings/agent-configuration'), + render: renderAsRedirectTo('/settings/agent-configuration'), breadcrumb: i18n.translate('xpack.apm.breadcrumb.listSettingsTitle', { defaultMessage: 'Settings', }), @@ -219,7 +210,7 @@ export const routes: APMRouteDefinition[] = [ exact: true, path: '/services/:serviceName', breadcrumb: ({ match }) => match.params.serviceName, - component: (props: RouteComponentProps<{ serviceName: string }>) => + render: (props: RouteComponentProps<{ serviceName: string }>) => renderAsRedirectTo( `/services/${props.match.params.serviceName}/transactions` )(props), diff --git a/x-pack/plugins/apm/public/components/app/Main/route_config/route_config.test.tsx b/x-pack/plugins/apm/public/components/app/Main/route_config/route_config.test.tsx index 21a162111bc79..ba3641cc4dadd 100644 --- a/x-pack/plugins/apm/public/components/app/Main/route_config/route_config.test.tsx +++ b/x-pack/plugins/apm/public/components/app/Main/route_config/route_config.test.tsx @@ -14,7 +14,7 @@ describe('routes', () => { it('redirects to /services', () => { const location = { hash: '', pathname: '/', search: '' }; expect( - (route as any).component({ location } as any).props.to.pathname + (route!.render!({ location } as any) as any).props.to.pathname ).toEqual('/services'); }); }); @@ -28,9 +28,7 @@ describe('routes', () => { search: '', }; - expect( - ((route as any).component({ location }) as any).props.to - ).toEqual({ + expect((route!.render!({ location } as any) as any).props.to).toEqual({ hash: '', pathname: '/services/opbeans-python/transactions/view', search: diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx index c76be19edfe47..904144dec6de9 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx @@ -33,7 +33,10 @@ import { ChartWrapper } from '../ChartWrapper'; import { I18LABELS } from '../translations'; interface Props { - data?: Array>; + data?: { + topItems: string[]; + items: Array>; + }; loading: boolean; } @@ -68,15 +71,9 @@ export function PageViewsChart({ data, loading }: Props) { }); }; - let breakdownAccessors: Set = new Set(); - if (data && data.length > 0) { - data.forEach((item) => { - breakdownAccessors = new Set([ - ...Array.from(breakdownAccessors), - ...Object.keys(item).filter((key) => key !== 'x'), - ]); - }); - } + const breakdownAccessors = data?.topItems?.length ? data?.topItems : ['y']; + + const [darkMode] = useUiSetting$('theme:darkMode'); const customSeriesNaming: SeriesNameFn = ({ yAccessor }) => { if (yAccessor === 'y') { @@ -86,8 +83,6 @@ export function PageViewsChart({ data, loading }: Props) { return yAccessor; }; - const [darkMode] = useUiSetting$('theme:darkMode'); - return ( {(!loading || data) && ( @@ -115,7 +110,8 @@ export function PageViewsChart({ data, loading }: Props) { id="page_views" title={I18LABELS.pageViews} position={Position.Left} - tickFormat={(d) => numeral(d).format('0a')} + tickFormat={(d) => numeral(d).format('0')} + labelFormat={(d) => numeral(d).format('0a')} /> diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx index 1edfd724dadd7..a77d27c4bc883 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx @@ -22,7 +22,7 @@ const ClFlexGroup = styled(EuiFlexGroup)` export function ClientMetrics() { const { urlParams, uiFilters } = useUrlParams(); - const { start, end } = urlParams; + const { start, end, searchTerm } = urlParams; const { data, status } = useFetcher( (callApmApi) => { @@ -31,13 +31,18 @@ export function ClientMetrics() { return callApmApi({ pathname: '/api/apm/rum/client-metrics', params: { - query: { start, end, uiFilters: JSON.stringify(uiFilters) }, + query: { + start, + end, + uiFilters: JSON.stringify(uiFilters), + urlQuery: searchTerm, + }, }, }); } return Promise.resolve(null); }, - [start, end, uiFilters] + [start, end, uiFilters, searchTerm] ); const STAT_STYLE = { width: '240px' }; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx index f97db3b42eecb..c8e45d2b2f672 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx @@ -22,7 +22,7 @@ export interface PercentileRange { export function PageLoadDistribution() { const { urlParams, uiFilters } = useUrlParams(); - const { start, end } = urlParams; + const { start, end, searchTerm } = urlParams; const [percentileRange, setPercentileRange] = useState({ min: null, @@ -41,6 +41,7 @@ export function PageLoadDistribution() { start, end, uiFilters: JSON.stringify(uiFilters), + urlQuery: searchTerm, ...(percentileRange.min && percentileRange.max ? { minPercentile: String(percentileRange.min), @@ -53,7 +54,14 @@ export function PageLoadDistribution() { } return Promise.resolve(null); }, - [end, start, uiFilters, percentileRange.min, percentileRange.max] + [ + end, + start, + uiFilters, + percentileRange.min, + percentileRange.max, + searchTerm, + ] ); const onPercentileChange = (min: number, max: number) => { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts index 814cf977c9569..d6a544333531f 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts @@ -17,7 +17,7 @@ interface Props { export const useBreakdowns = ({ percentileRange, field, value }: Props) => { const { urlParams, uiFilters } = useUrlParams(); - const { start, end } = urlParams; + const { start, end, searchTerm } = urlParams; const { min: minP, max: maxP } = percentileRange ?? {}; @@ -32,6 +32,7 @@ export const useBreakdowns = ({ percentileRange, field, value }: Props) => { end, breakdown: value, uiFilters: JSON.stringify(uiFilters), + urlQuery: searchTerm, ...(minP && maxP ? { minPercentile: String(minP), @@ -43,6 +44,6 @@ export const useBreakdowns = ({ percentileRange, field, value }: Props) => { }); } }, - [end, start, uiFilters, field, value, minP, maxP] + [end, start, uiFilters, field, value, minP, maxP, searchTerm] ); }; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx index 2991f9a15f085..f2da0955412e7 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx @@ -16,7 +16,7 @@ import { BreakdownItem } from '../../../../../typings/ui_filters'; export function PageViewsTrend() { const { urlParams, uiFilters } = useUrlParams(); - const { start, end } = urlParams; + const { start, end, searchTerm } = urlParams; const [breakdown, setBreakdown] = useState(null); @@ -30,6 +30,7 @@ export function PageViewsTrend() { start, end, uiFilters: JSON.stringify(uiFilters), + urlQuery: searchTerm, ...(breakdown ? { breakdowns: JSON.stringify(breakdown), @@ -41,7 +42,7 @@ export function PageViewsTrend() { } return Promise.resolve(undefined); }, - [end, start, uiFilters, breakdown] + [end, start, uiFilters, breakdown, searchTerm] ); return ( diff --git a/x-pack/plugins/apm/public/components/shared/LocalUIFilters/ServiceNameFilter/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx similarity index 94% rename from x-pack/plugins/apm/public/components/shared/LocalUIFilters/ServiceNameFilter/index.tsx rename to x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx index cbf9ba009dce2..f10c9e888a193 100644 --- a/x-pack/plugins/apm/public/components/shared/LocalUIFilters/ServiceNameFilter/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx @@ -13,8 +13,8 @@ import { import { i18n } from '@kbn/i18n'; import React, { useEffect, useCallback } from 'react'; import { useHistory } from 'react-router-dom'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; -import { fromQuery, toQuery } from '../../Links/url_helpers'; +import { useUrlParams } from '../../../../../hooks/useUrlParams'; +import { fromQuery, toQuery } from '../../../../shared/Links/url_helpers'; interface Props { serviceNames: string[]; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/RenderOption.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/RenderOption.tsx new file mode 100644 index 0000000000000..1a6f4e25fc315 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/RenderOption.tsx @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { ReactNode } from 'react'; +import classNames from 'classnames'; +import { EuiHighlight, EuiSelectableOption } from '@elastic/eui'; +import styled from 'styled-components'; +import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; + +const StyledSpan = styled.span` + color: ${euiLightVars.euiColorSecondaryText}; + font-weight: 500; + :not(:last-of-type)::after { + content: '•'; + margin: 0 4px; + } +`; + +const StyledListSpan = styled.span` + display: block; + margin-top: 4px; + font-size: 12px; +`; +export type UrlOption = { + meta?: string[]; +} & EuiSelectableOption; + +export const formatOptions = (options: EuiSelectableOption[]) => { + return options.map((item: EuiSelectableOption) => ({ + title: item.label, + ...item, + className: classNames( + 'euiSelectableTemplateSitewide__listItem', + item.className + ), + })); +}; + +export function selectableRenderOptions( + option: UrlOption, + searchValue: string +) { + return ( + <> + + {option.label} + + {renderOptionMeta(option.meta)} + + ); +} + +function renderOptionMeta(meta?: string[]): ReactNode { + if (!meta || meta.length < 1) return; + return ( + + {meta.map((item: string) => ( + {item} + ))} + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.tsx new file mode 100644 index 0000000000000..298ec15b8480b --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.tsx @@ -0,0 +1,164 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FormEvent, useRef, useState } from 'react'; +import { + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiLoadingSpinner, + EuiPopover, + EuiPopoverTitle, + EuiSelectable, + EuiSelectableMessage, +} from '@elastic/eui'; +import { + formatOptions, + selectableRenderOptions, + UrlOption, +} from './RenderOption'; +import { I18LABELS } from '../../translations'; + +interface Props { + data: { + items: UrlOption[]; + total?: number; + }; + loading: boolean; + onInputChange: (e: FormEvent) => void; + onTermChange: () => void; + onChange: (updatedOptions: UrlOption[]) => void; + searchValue: string; + onClose: () => void; +} + +export function SelectableUrlList({ + data, + loading, + onInputChange, + onTermChange, + onChange, + searchValue, + onClose, +}: Props) { + const [popoverIsOpen, setPopoverIsOpen] = useState(false); + const [popoverRef, setPopoverRef] = useState(null); + const [searchRef, setSearchRef] = useState(null); + + const titleRef = useRef(null); + + const searchOnFocus = (e: React.FocusEvent) => { + setPopoverIsOpen(true); + }; + + const onSearchInput = (e: React.FormEvent) => { + onInputChange(e); + setPopoverIsOpen(true); + }; + + const searchOnBlur = (e: React.FocusEvent) => { + if ( + !popoverRef?.contains(e.relatedTarget as HTMLElement) && + !popoverRef?.contains(titleRef.current as HTMLDivElement) + ) { + setPopoverIsOpen(false); + } + }; + + const formattedOptions = formatOptions(data.items ?? []); + + const closePopover = () => { + setPopoverIsOpen(false); + onClose(); + if (searchRef) { + searchRef.blur(); + } + }; + + const loadingMessage = ( + + +
+

{I18LABELS.loadingResults}

+
+ ); + + const emptyMessage = ( + +

{I18LABELS.noResults}

+
+ ); + + const titleText = searchValue + ? I18LABELS.getSearchResultsLabel(data?.total ?? 0) + : I18LABELS.topPages; + + function PopOverTitle() { + return ( + + + + {loading ? : titleText} + + + { + onTermChange(); + setPopoverIsOpen(false); + }} + > + {I18LABELS.matchThisQuery} + + + + + ); + } + + return ( + + {(list, search) => ( + +
+ + {list} +
+
+ )} +
+ ); +} diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx new file mode 100644 index 0000000000000..b88cf29740dcd --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx @@ -0,0 +1,132 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiTitle } from '@elastic/eui'; +import useDebounce from 'react-use/lib/useDebounce'; +import React, { useEffect, useState, FormEvent, useCallback } from 'react'; +import { useHistory } from 'react-router-dom'; +import { useUrlParams } from '../../../../../hooks/useUrlParams'; +import { useFetcher } from '../../../../../hooks/useFetcher'; +import { I18LABELS } from '../../translations'; +import { fromQuery, toQuery } from '../../../../shared/Links/url_helpers'; +import { formatToSec } from '../../UXMetrics/KeyUXMetrics'; +import { SelectableUrlList } from './SelectableUrlList'; +import { UrlOption } from './RenderOption'; + +interface Props { + onChange: (value: string[]) => void; +} + +export function URLSearch({ onChange: onFilterChange }: Props) { + const history = useHistory(); + + const { urlParams, uiFilters } = useUrlParams(); + + const { start, end, serviceName } = urlParams; + const [searchValue, setSearchValue] = useState(''); + + const [debouncedValue, setDebouncedValue] = useState(''); + + useDebounce( + () => { + setSearchValue(debouncedValue); + }, + 250, + [debouncedValue] + ); + + const updateSearchTerm = useCallback( + (searchTermN: string) => { + const newLocation = { + ...history.location, + search: fromQuery({ + ...toQuery(history.location.search), + searchTerm: searchTermN, + }), + }; + history.push(newLocation); + }, + [history] + ); + + const [checkedUrls, setCheckedUrls] = useState([]); + + const { data, status } = useFetcher( + (callApmApi) => { + if (start && end && serviceName) { + const { transactionUrl, ...restFilters } = uiFilters; + + return callApmApi({ + pathname: '/api/apm/rum-client/url-search', + params: { + query: { + start, + end, + uiFilters: JSON.stringify(restFilters), + urlQuery: searchValue, + }, + }, + }); + } + return Promise.resolve(null); + }, + [start, end, serviceName, uiFilters, searchValue] + ); + + useEffect(() => { + setCheckedUrls(uiFilters.transactionUrl || []); + }, [uiFilters]); + + const onChange = (updatedOptions: UrlOption[]) => { + const clickedItems = updatedOptions.filter( + (option) => option.checked === 'on' + ); + + setCheckedUrls(clickedItems.map((item) => item.url)); + }; + + const items: UrlOption[] = (data?.items ?? []).map((item) => ({ + label: item.url, + key: item.url, + meta: [ + I18LABELS.pageViews + ': ' + item.count, + I18LABELS.pageLoadDuration + ': ' + formatToSec(item.pld), + ], + url: item.url, + checked: checkedUrls?.includes(item.url) ? 'on' : undefined, + })); + + const onInputChange = (e: FormEvent) => { + setDebouncedValue(e.currentTarget.value); + }; + + const isLoading = status !== 'success'; + + const onTermChange = () => { + updateSearchTerm(searchValue); + }; + + const onClose = () => { + onFilterChange(checkedUrls); + }; + + return ( + <> + +

{I18LABELS.url}

+
+ + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/UrlList.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/UrlList.tsx new file mode 100644 index 0000000000000..437c005db37b0 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/UrlList.tsx @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiFlexGrid, EuiFlexItem, EuiBadge } from '@elastic/eui'; +import styled from 'styled-components'; +import { i18n } from '@kbn/i18n'; +import { px, truncate, unit } from '../../../../style/variables'; + +const BadgeText = styled.div` + display: inline-block; + ${truncate(px(unit * 12))}; + vertical-align: middle; +`; + +interface Props { + value: string[]; + onRemove: (val: string) => void; +} + +const formatUrlValue = (val: string) => { + const maxUrlToDisplay = 30; + const urlLength = val.length; + if (urlLength < maxUrlToDisplay) { + return val; + } + const urlObj = new URL(val); + if (urlObj.pathname === '/') { + return val; + } + const domainVal = urlObj.hostname; + const extraLength = urlLength - maxUrlToDisplay; + const extraDomain = domainVal.substring(0, extraLength); + + if (urlObj.pathname.length + 7 > maxUrlToDisplay) { + return val.replace(domainVal, '..'); + } + + return val.replace(extraDomain, '..'); +}; + +const removeFilterLabel = i18n.translate( + 'xpack.apm.uifilter.badge.removeFilter', + { defaultMessage: 'Remove filter' } +); + +export function UrlList({ onRemove, value }: Props) { + return ( + + {value.map((val) => ( + + { + onRemove(val); + }} + onClickAriaLabel={removeFilterLabel} + iconOnClick={() => { + onRemove(val); + }} + iconOnClickAriaLabel={removeFilterLabel} + iconType="cross" + iconSide="right" + > + {formatUrlValue(val)} + + + ))} + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/index.tsx new file mode 100644 index 0000000000000..9d3c8d012871f --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/index.tsx @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import React, { useCallback, useMemo } from 'react'; +import { EuiSpacer, EuiBadge } from '@elastic/eui'; +import { useHistory } from 'react-router-dom'; +import { Projection } from '../../../../../common/projections'; +import { useLocalUIFilters } from '../../../../hooks/useLocalUIFilters'; +import { URLSearch } from './URLSearch'; +import { LocalUIFilters } from '../../../shared/LocalUIFilters'; +import { UrlList } from './UrlList'; +import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { fromQuery, toQuery } from '../../../shared/Links/url_helpers'; + +const removeSearchTermLabel = i18n.translate( + 'xpack.apm.uiFilter.url.removeSearchTerm', + { defaultMessage: 'Clear url query' } +); + +export function URLFilter() { + const history = useHistory(); + + const { + urlParams: { searchTerm }, + } = useUrlParams(); + + const localUIFiltersConfig = useMemo(() => { + const config: React.ComponentProps = { + filterNames: ['transactionUrl'], + projection: Projection.rumOverview, + }; + + return config; + }, []); + + const { filters, setFilterValue } = useLocalUIFilters({ + ...localUIFiltersConfig, + }); + + const updateSearchTerm = useCallback( + (searchTermN?: string) => { + const newLocation = { + ...history.location, + search: fromQuery({ + ...toQuery(history.location.search), + searchTerm: searchTermN, + }), + }; + history.push(newLocation); + }, + [history] + ); + + const { name, value: filterValue } = filters[0]; + + return ( + + + { + setFilterValue('transactionUrl', value); + }} + /> + + {searchTerm && ( + <> + { + updateSearchTerm(); + }} + onClickAriaLabel={removeSearchTermLabel} + iconOnClick={() => { + updateSearchTerm(); + }} + iconOnClickAriaLabel={removeSearchTermLabel} + iconType="cross" + iconSide="right" + > + *{searchTerm}* + + + + )} + {filterValue.length > 0 && ( + { + setFilterValue( + name, + filterValue.filter((v) => val !== v) + ); + }} + value={filterValue} + /> + )} + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx index 5c9a636adec8f..1d8360872afba 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx @@ -38,7 +38,7 @@ interface Props { export function KeyUXMetrics({ data, loading }: Props) { const { urlParams, uiFilters } = useUrlParams(); - const { start, end, serviceName } = urlParams; + const { start, end, serviceName, searchTerm } = urlParams; const { data: longTaskData, status } = useFetcher( (callApmApi) => { @@ -46,13 +46,18 @@ export function KeyUXMetrics({ data, loading }: Props) { return callApmApi({ pathname: '/api/apm/rum-client/long-task-metrics', params: { - query: { start, end, uiFilters: JSON.stringify(uiFilters) }, + query: { + start, + end, + uiFilters: JSON.stringify(uiFilters), + urlQuery: searchTerm, + }, }, }); } return Promise.resolve(null); }, - [start, end, serviceName, uiFilters] + [start, end, serviceName, uiFilters, searchTerm] ); // Note: FCP value is in ms unit diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx index 94c3acfaa9727..3c7b4e39401de 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx @@ -33,7 +33,7 @@ export interface UXMetrics { export function UXMetrics() { const { urlParams, uiFilters } = useUrlParams(); - const { start, end } = urlParams; + const { start, end, searchTerm } = urlParams; const { data, status } = useFetcher( (callApmApi) => { @@ -42,13 +42,18 @@ export function UXMetrics() { return callApmApi({ pathname: '/api/apm/rum-client/web-core-vitals', params: { - query: { start, end, uiFilters: JSON.stringify(uiFilters) }, + query: { + start, + end, + uiFilters: JSON.stringify(uiFilters), + urlQuery: searchTerm, + }, }, }); } return Promise.resolve(null); }, - [start, end, uiFilters] + [start, end, uiFilters, searchTerm] ); return ( diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx index 245f58370d3d7..2db6ef8fa6c06 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx @@ -14,7 +14,7 @@ import { useUrlParams } from '../../../../hooks/useUrlParams'; export function VisitorBreakdown() { const { urlParams, uiFilters } = useUrlParams(); - const { start, end } = urlParams; + const { start, end, searchTerm } = urlParams; const { data, status } = useFetcher( (callApmApi) => { @@ -26,13 +26,14 @@ export function VisitorBreakdown() { start, end, uiFilters: JSON.stringify(uiFilters), + urlQuery: searchTerm, }, }, }); } return Promise.resolve(null); }, - [end, start, uiFilters] + [end, start, uiFilters, searchTerm] ); return ( diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/index.tsx index fa0551252b6a1..588831d55771d 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/index.tsx @@ -12,14 +12,15 @@ import { EuiSpacer, } from '@elastic/eui'; import { useTrackPageview } from '../../../../../observability/public'; -import { LocalUIFilters } from '../../shared/LocalUIFilters'; import { Projection } from '../../../../common/projections'; import { RumDashboard } from './RumDashboard'; -import { ServiceNameFilter } from '../../shared/LocalUIFilters/ServiceNameFilter'; import { useUrlParams } from '../../../hooks/useUrlParams'; import { useFetcher } from '../../../hooks/useFetcher'; import { RUM_AGENTS } from '../../../../common/agent_name'; import { EnvironmentFilter } from '../../shared/EnvironmentFilter'; +import { URLFilter } from './URLFilter'; +import { LocalUIFilters } from '../../shared/LocalUIFilters'; +import { ServiceNameFilter } from './URLFilter/ServiceNameFilter'; export function RumOverview() { useTrackPageview({ app: 'apm', path: 'rum_overview' }); @@ -27,7 +28,7 @@ export function RumOverview() { const localUIFiltersConfig = useMemo(() => { const config: React.ComponentProps = { - filterNames: ['transactionUrl', 'location', 'device', 'os', 'browser'], + filterNames: ['location', 'device', 'os', 'browser'], projection: Projection.rumOverview, }; @@ -63,6 +64,7 @@ export function RumOverview() { + <> + {' '} diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts index 1fafb7d1ed4d0..714788ef468c6 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts @@ -79,6 +79,32 @@ export const I18LABELS = { defaultMessage: 'Page load duration by region', } ), + searchByUrl: i18n.translate('xpack.apm.rum.filters.searchByUrl', { + defaultMessage: 'Search by url', + }), + getSearchResultsLabel: (total: number) => + i18n.translate('xpack.apm.rum.filters.searchResults', { + defaultMessage: '{total} Search results', + values: { total }, + }), + topPages: i18n.translate('xpack.apm.rum.filters.topPages', { + defaultMessage: 'Top pages', + }), + select: i18n.translate('xpack.apm.rum.filters.select', { + defaultMessage: 'Select', + }), + url: i18n.translate('xpack.apm.rum.filters.url', { + defaultMessage: 'Url', + }), + matchThisQuery: i18n.translate('xpack.apm.rum.filters.url.matchThisQuery', { + defaultMessage: 'Match this query', + }), + loadingResults: i18n.translate('xpack.apm.rum.filters.url.loadingResults', { + defaultMessage: 'Loading results', + }), + noResults: i18n.translate('xpack.apm.rum.filters.url.noResults', { + defaultMessage: 'No results available', + }), }; export const VisitorBreakdownLabel = i18n.translate( diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/BetaBadge.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/BetaBadge.tsx deleted file mode 100644 index b468470e3a17d..0000000000000 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/BetaBadge.tsx +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { EuiBetaBadge } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React from 'react'; -import styled from 'styled-components'; - -const BetaBadgeContainer = styled.div` - right: ${({ theme }) => theme.eui.gutterTypes.gutterMedium}; - position: absolute; - top: ${({ theme }) => theme.eui.gutterTypes.gutterSmall}; - z-index: 1; /* The element containing the cytoscape canvas has z-index = 0. */ -`; - -export function BetaBadge() { - return ( - - - - ); -} diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx index cb5a57e9ab9fb..bb450131bdfb8 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx @@ -4,15 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { useTheme } from '../../../hooks/useTheme'; +import React from 'react'; +import { useTrackPageview } from '../../../../../observability/public'; import { invalidLicenseMessage, isActivePlatinumLicense, } from '../../../../common/service_map'; import { useFetcher } from '../../../hooks/useFetcher'; import { useLicense } from '../../../hooks/useLicense'; +import { useTheme } from '../../../hooks/useTheme'; import { useUrlParams } from '../../../hooks/useUrlParams'; import { callApmApi } from '../../../services/rest/createCallApmApi'; import { LicensePrompt } from '../../shared/LicensePrompt'; @@ -22,8 +23,6 @@ import { getCytoscapeDivStyle } from './cytoscapeOptions'; import { EmptyBanner } from './EmptyBanner'; import { Popover } from './Popover'; import { useRefDimensions } from './useRefDimensions'; -import { BetaBadge } from './BetaBadge'; -import { useTrackPageview } from '../../../../../observability/public'; interface ServiceMapProps { serviceName?: string; @@ -80,7 +79,6 @@ export function ServiceMap({ serviceName }: ServiceMapProps) { style={getCytoscapeDivStyle(theme)} > - {serviceName && } @@ -96,7 +94,7 @@ export function ServiceMap({ serviceName }: ServiceMapProps) { grow={false} style={{ width: 600, textAlign: 'center' as const }} > - + ); diff --git a/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts b/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts index cb30c6c064848..49030dc8cacc5 100644 --- a/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts +++ b/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts @@ -8,7 +8,7 @@ import LRU from 'lru-cache'; import { LegacyAPICaller } from '../../../../../../src/core/server'; import { IndexPatternsFetcher, - IIndexPattern, + FieldDescriptor, } from '../../../../../../src/plugins/data/server'; import { ApmIndicesConfig } from '../settings/apm_indices/get_apm_indices'; import { @@ -17,7 +17,12 @@ import { } from '../../../common/processor_event'; import { APMRequestHandlerContext } from '../../routes/typings'; -const cache = new LRU({ +interface IndexPatternTitleAndFields { + title: string; + fields: FieldDescriptor[]; +} + +const cache = new LRU({ max: 100, maxAge: 1000 * 60, }); @@ -53,7 +58,7 @@ export const getDynamicIndexPattern = async ({ pattern: patternIndices, }); - const indexPattern: IIndexPattern = { + const indexPattern: IndexPatternTitleAndFields = { fields, title: indexPatternTitle, }; 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 b3f9646f64029..cf4a5538a208d 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 @@ -19,11 +19,14 @@ import { export async function getClientMetrics({ setup, + urlQuery, }: { setup: Setup & SetupTimeRange & SetupUIFilters; + urlQuery?: string; }) { const projection = getRumPageLoadTransactionsProjection({ setup, + urlQuery, }); const params = mergeProjection(projection, { 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 1faee52034580..812cf9865bda8 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 @@ -14,12 +14,17 @@ import { SetupTimeRange, SetupUIFilters, } from '../helpers/setup_request'; -import { SPAN_DURATION } from '../../../common/elasticsearch_fieldnames'; +import { + SPAN_DURATION, + TRANSACTION_ID, +} from '../../../common/elasticsearch_fieldnames'; export async function getLongTaskMetrics({ setup, + urlQuery, }: { setup: Setup & SetupTimeRange & SetupUIFilters; + urlQuery?: string; }) { const projection = getRumLongTasksProjection({ setup, @@ -28,9 +33,6 @@ export async function getLongTaskMetrics({ const params = mergeProjection(projection, { body: { size: 0, - query: { - bool: projection.body.query.bool, - }, aggs: { transIds: { terms: { @@ -59,10 +61,13 @@ export async function getLongTaskMetrics({ const response = await apmEventClient.search(params); const { transIds } = response.aggregations ?? {}; - const validTransactions: string[] = await filterPageLoadTransactions( + const validTransactions: string[] = await filterPageLoadTransactions({ setup, - (transIds?.buckets ?? []).map((bucket) => bucket.key as string) - ); + urlQuery, + transactionIds: (transIds?.buckets ?? []).map( + (bucket) => bucket.key as string + ), + }); let noOfLongTasks = 0; let sumOfLongTasks = 0; let longestLongTask = 0; @@ -83,12 +88,18 @@ export async function getLongTaskMetrics({ }; } -async function filterPageLoadTransactions( - setup: Setup & SetupTimeRange & SetupUIFilters, - transactionIds: string[] -) { +async function filterPageLoadTransactions({ + setup, + urlQuery, + transactionIds, +}: { + setup: Setup & SetupTimeRange & SetupUIFilters; + urlQuery?: string; + transactionIds: string[]; +}) { const projection = getRumPageLoadTransactionsProjection({ setup, + urlQuery, }); const params = mergeProjection(projection, { @@ -99,14 +110,14 @@ async function filterPageLoadTransactions( must: [ { terms: { - 'transaction.id': transactionIds, + [TRANSACTION_ID]: transactionIds, }, }, ], filter: [...projection.body.query.bool.filter], }, }, - _source: ['transaction.id'], + _source: [TRANSACTION_ID], }, }); 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 3d8ab7a72654d..25de9f06fefc4 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 @@ -40,13 +40,16 @@ export async function getPageLoadDistribution({ setup, minPercentile, maxPercentile, + urlQuery, }: { setup: Setup & SetupTimeRange & SetupUIFilters; minPercentile?: string; maxPercentile?: string; + urlQuery?: string; }) { const projection = getRumPageLoadTransactionsProjection({ setup, + urlQuery, }); const params = mergeProjection(projection, { 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 f25062c67f87a..ef4f8b16e0e7b 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 @@ -18,6 +18,7 @@ export async function getPageViewTrends({ }: { setup: Setup & SetupTimeRange & SetupUIFilters; breakdowns?: string; + urlQuery?: string; }) { const projection = getRumPageLoadTransactionsProjection({ setup, @@ -51,6 +52,16 @@ export async function getPageViewTrends({ } : undefined, }, + ...(breakdownItem + ? { + topBreakdowns: { + terms: { + field: breakdownItem.fieldName, + size: 9, + }, + }, + } + : {}), }, }, }); @@ -59,25 +70,44 @@ export async function getPageViewTrends({ const response = await apmEventClient.search(params); + const { topBreakdowns } = response.aggregations ?? {}; + + // we are only displaying top 9 + const topItems: string[] = (topBreakdowns?.buckets ?? []).map( + ({ key }) => key as string + ); + const result = response.aggregations?.pageViews.buckets ?? []; - return result.map((bucket) => { - const { key: xVal, doc_count: bCount } = bucket; - const res: Record = { - x: xVal, - y: bCount, - }; - if ('breakdown' in bucket) { - const categoryBuckets = bucket.breakdown.buckets; - categoryBuckets.forEach(({ key, doc_count: docCount }) => { - if (key === 'Other') { - res[key + `(${breakdownItem?.name})`] = docCount; - } else { - res[key] = docCount; + return { + topItems, + items: result.map((bucket) => { + const { key: xVal, doc_count: bCount } = bucket; + const res: Record = { + x: xVal, + y: bCount, + }; + if ('breakdown' in bucket) { + let top9Count = 0; + const categoryBuckets = bucket.breakdown.buckets; + categoryBuckets.forEach(({ key, doc_count: docCount }) => { + if (topItems.includes(key as string)) { + if (res[key]) { + // if term is already in object, just add it to it + res[key] += docCount; + } else { + res[key] = docCount; + } + top9Count += docCount; + } + }); + // Top 9 plus others, get a diff from parent bucket total + if (bCount > top9Count) { + res.Other = bCount - top9Count; } - }); - } + } - return res; - }); + return res; + }), + }; } 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 1945140e35777..d59817cc682a9 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 @@ -44,11 +44,13 @@ export const getPageLoadDistBreakdown = async ({ minDuration, maxDuration, breakdown, + urlQuery, }: { setup: Setup & SetupTimeRange & SetupUIFilters; minDuration: number; maxDuration: number; breakdown: string; + urlQuery?: string; }) => { // convert secs to micros const stepValues = getPLDChartSteps({ @@ -58,6 +60,7 @@ export const getPageLoadDistBreakdown = async ({ const projection = getRumPageLoadTransactionsProjection({ setup, + urlQuery, }); const params = mergeProjection(projection, { 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 new file mode 100644 index 0000000000000..a7117f275c17b --- /dev/null +++ b/x-pack/plugins/apm/server/lib/rum_client/get_url_search.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { mergeProjection } from '../../projections/util/merge_projection'; +import { + Setup, + SetupTimeRange, + SetupUIFilters, +} from '../helpers/setup_request'; +import { getRumPageLoadTransactionsProjection } from '../../projections/rum_page_load_transactions'; + +export async function getUrlSearch({ + setup, + urlQuery, +}: { + setup: Setup & SetupTimeRange & SetupUIFilters; + urlQuery?: string; +}) { + const projection = getRumPageLoadTransactionsProjection({ + setup, + urlQuery, + }); + + const params = mergeProjection(projection, { + body: { + size: 0, + aggs: { + totalUrls: { + cardinality: { + field: 'url.full', + }, + }, + urls: { + terms: { + field: 'url.full', + size: 10, + }, + aggs: { + medianPLD: { + percentiles: { + field: 'transaction.duration.us', + percents: [50], + }, + }, + }, + }, + }, + }, + }); + + const { apmEventClient } = setup; + + const response = await apmEventClient.search(params); + const { urls, totalUrls } = response.aggregations ?? {}; + + return { + total: totalUrls?.value || 0, + items: (urls?.buckets ?? []).map((bucket) => ({ + url: bucket.key as string, + count: bucket.doc_count, + pld: bucket.medianPLD.values['50.0'] ?? 0, + })), + }; +} 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 3493307929f42..1b4388afd7c5d 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 @@ -19,11 +19,14 @@ import { export async function getVisitorBreakdown({ setup, + urlQuery, }: { setup: Setup & SetupTimeRange & SetupUIFilters; + urlQuery?: string; }) { const projection = getRumPageLoadTransactionsProjection({ setup, + urlQuery, }); const params = mergeProjection(projection, { 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 2ff0173b9ac12..fa34c2e25fecd 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 @@ -22,8 +22,10 @@ import { export async function getWebCoreVitals({ setup, + urlQuery, }: { setup: Setup & SetupTimeRange & SetupUIFilters; + urlQuery?: string; }) { const projection = getRumPageLoadTransactionsProjection({ setup, 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 27cd9b53f8349..3c3eaaca7efdb 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 @@ -19,8 +19,10 @@ import { TRANSACTION_PAGE_LOAD } from '../../common/transaction_types'; export function getRumPageLoadTransactionsProjection({ setup, + urlQuery, }: { setup: Setup & SetupTimeRange & SetupUIFilters; + urlQuery?: string; }) { const { start, end, uiFiltersES } = setup; @@ -35,6 +37,17 @@ export function getRumPageLoadTransactionsProjection({ field: 'transaction.marks.navigationTiming.fetchStart', }, }, + ...(urlQuery + ? [ + { + wildcard: { + 'url.full': { + value: `*${urlQuery}*`, + }, + }, + }, + ] + : []), ...uiFiltersES, ], }; diff --git a/x-pack/plugins/apm/server/routes/create_apm_api.ts b/x-pack/plugins/apm/server/routes/create_apm_api.ts index 7d9a9ccc167e0..f975ab177f147 100644 --- a/x-pack/plugins/apm/server/routes/create_apm_api.ts +++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts @@ -77,6 +77,7 @@ import { rumServicesRoute, rumVisitorsBreakdownRoute, rumWebCoreVitals, + rumUrlSearch, rumLongTaskMetrics, } from './rum_client'; import { @@ -173,6 +174,7 @@ const createApmApi = () => { .add(rumServicesRoute) .add(rumVisitorsBreakdownRoute) .add(rumWebCoreVitals) + .add(rumUrlSearch) .add(rumLongTaskMetrics) // Observability dashboard diff --git a/x-pack/plugins/apm/server/routes/rum_client.ts b/x-pack/plugins/apm/server/routes/rum_client.ts index 179279b6f2d8a..e3a846f9fb5c7 100644 --- a/x-pack/plugins/apm/server/routes/rum_client.ts +++ b/x-pack/plugins/apm/server/routes/rum_client.ts @@ -16,37 +16,54 @@ import { getRumServices } from '../lib/rum_client/get_rum_services'; import { getVisitorBreakdown } from '../lib/rum_client/get_visitor_breakdown'; import { getWebCoreVitals } from '../lib/rum_client/get_web_core_vitals'; import { getLongTaskMetrics } from '../lib/rum_client/get_long_task_metrics'; +import { getUrlSearch } from '../lib/rum_client/get_url_search'; export const percentileRangeRt = t.partial({ minPercentile: t.string, maxPercentile: t.string, }); +const urlQueryRt = t.partial({ urlQuery: t.string }); + export const rumClientMetricsRoute = createRoute(() => ({ path: '/api/apm/rum/client-metrics', params: { - query: t.intersection([uiFiltersRt, rangeRt]), + query: t.intersection([uiFiltersRt, rangeRt, urlQueryRt]), }, handler: async ({ context, request }) => { const setup = await setupRequest(context, request); - return getClientMetrics({ setup }); + const { + query: { urlQuery }, + } = context.params; + + return getClientMetrics({ setup, urlQuery }); }, })); export const rumPageLoadDistributionRoute = createRoute(() => ({ path: '/api/apm/rum-client/page-load-distribution', params: { - query: t.intersection([uiFiltersRt, rangeRt, percentileRangeRt]), + query: t.intersection([ + uiFiltersRt, + rangeRt, + percentileRangeRt, + urlQueryRt, + ]), }, handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { - query: { minPercentile, maxPercentile }, + query: { minPercentile, maxPercentile, urlQuery }, } = context.params; - return getPageLoadDistribution({ setup, minPercentile, maxPercentile }); + return getPageLoadDistribution({ + setup, + minPercentile, + maxPercentile, + urlQuery, + }); }, })); @@ -57,6 +74,7 @@ export const rumPageLoadDistBreakdownRoute = createRoute(() => ({ uiFiltersRt, rangeRt, percentileRangeRt, + urlQueryRt, t.type({ breakdown: t.string }), ]), }, @@ -64,7 +82,7 @@ export const rumPageLoadDistBreakdownRoute = createRoute(() => ({ const setup = await setupRequest(context, request); const { - query: { minPercentile, maxPercentile, breakdown }, + query: { minPercentile, maxPercentile, breakdown, urlQuery }, } = context.params; return getPageLoadDistBreakdown({ @@ -72,6 +90,7 @@ export const rumPageLoadDistBreakdownRoute = createRoute(() => ({ minDuration: Number(minPercentile), maxDuration: Number(maxPercentile), breakdown, + urlQuery, }); }, })); @@ -82,6 +101,7 @@ export const rumPageViewsTrendRoute = createRoute(() => ({ query: t.intersection([ uiFiltersRt, rangeRt, + urlQueryRt, t.partial({ breakdowns: t.string }), ]), }, @@ -89,10 +109,10 @@ export const rumPageViewsTrendRoute = createRoute(() => ({ const setup = await setupRequest(context, request); const { - query: { breakdowns }, + query: { breakdowns, urlQuery }, } = context.params; - return getPageViewTrends({ setup, breakdowns }); + return getPageViewTrends({ setup, breakdowns, urlQuery }); }, })); @@ -111,35 +131,63 @@ export const rumServicesRoute = createRoute(() => ({ export const rumVisitorsBreakdownRoute = createRoute(() => ({ path: '/api/apm/rum-client/visitor-breakdown', params: { - query: t.intersection([uiFiltersRt, rangeRt]), + query: t.intersection([uiFiltersRt, rangeRt, urlQueryRt]), }, handler: async ({ context, request }) => { const setup = await setupRequest(context, request); - return getVisitorBreakdown({ setup }); + const { + query: { urlQuery }, + } = context.params; + + return getVisitorBreakdown({ setup, urlQuery }); }, })); export const rumWebCoreVitals = createRoute(() => ({ path: '/api/apm/rum-client/web-core-vitals', params: { - query: t.intersection([uiFiltersRt, rangeRt]), + query: t.intersection([uiFiltersRt, rangeRt, urlQueryRt]), }, handler: async ({ context, request }) => { const setup = await setupRequest(context, request); - return getWebCoreVitals({ setup }); + const { + query: { urlQuery }, + } = context.params; + + return getWebCoreVitals({ setup, urlQuery }); }, })); export const rumLongTaskMetrics = createRoute(() => ({ path: '/api/apm/rum-client/long-task-metrics', params: { - query: t.intersection([uiFiltersRt, rangeRt]), + query: t.intersection([uiFiltersRt, rangeRt, urlQueryRt]), }, handler: async ({ context, request }) => { const setup = await setupRequest(context, request); - return getLongTaskMetrics({ setup }); + const { + query: { urlQuery }, + } = context.params; + + return getLongTaskMetrics({ setup, urlQuery }); + }, +})); + +export const rumUrlSearch = createRoute(() => ({ + path: '/api/apm/rum-client/url-search', + params: { + query: t.intersection([uiFiltersRt, rangeRt, urlQueryRt]), + }, + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + + const { + query: { urlQuery }, + } = context.params; + + return getUrlSearch({ setup, urlQuery }); }, })); diff --git a/x-pack/plugins/apm/typings/apm_rum_react.d.ts b/x-pack/plugins/apm/typings/apm_rum_react.d.ts index 1c3e41ec12780..f9eafef59f55d 100644 --- a/x-pack/plugins/apm/typings/apm_rum_react.d.ts +++ b/x-pack/plugins/apm/typings/apm_rum_react.d.ts @@ -3,6 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + declare module '@elastic/apm-rum-react' { - export const ApmRoute: any; + import { RouteProps } from 'react-router-dom'; + + export const ApmRoute: React.ComponentClass; } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_lens.test.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_lens.test.ts index 33260b5c9303f..df205dc742b07 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_lens.test.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_lens.test.ts @@ -49,4 +49,9 @@ describe('savedLens', () => { expect(expression.input.filters).toEqual(embeddableFilters); }); + + it('accepts an empty title when title is disabled', () => { + const expression = fn(null, { ...args, title: '' }, {} as any); + expect(expression.input.title).toEqual(''); + }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_lens.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_lens.ts index 49b8c5562af65..a823d0606d46f 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_lens.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_lens.ts @@ -72,7 +72,7 @@ export function savedLens(): ExpressionFunctionDefinition< id: args.id, filters: getQueryFilters(filters), timeRange: args.timerange || defaultTimeRange, - title: args.title ? args.title : undefined, + title: args.title === null ? undefined : args.title, disableTriggers: true, }, embeddableType: EmbeddableTypes.lens, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.ts index ec640cfb5b299..a64ff7da2aa19 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.ts @@ -93,7 +93,7 @@ export function savedMap(): ExpressionFunctionDefinition< mapCenter: center, hideFilterActions: true, - title: args.title ? args.title : undefined, + title: args.title === null ? undefined : args.title, isLayerTOCOpen: false, hiddenLayers: args.hideLayer || [], }, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_visualization.test.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_visualization.test.ts index a64fb167dd19f..7902d09a0bdf1 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_visualization.test.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_visualization.test.ts @@ -36,6 +36,7 @@ describe('savedVisualization', () => { timerange: null, colors: null, hideLegend: null, + title: null, }; it('accepts null context', () => { @@ -50,4 +51,9 @@ describe('savedVisualization', () => { expect(expression.input.filters).toEqual(embeddableFilters); }); + + it('accepts an empty title when title is disabled', () => { + const expression = fn(null, { ...args, title: '' }, {} as any); + expect(expression.input.title).toEqual(''); + }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_visualization.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_visualization.ts index 2782ca039d7ed..449be2db43d44 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_visualization.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_visualization.ts @@ -20,6 +20,7 @@ interface Arguments { timerange: TimeRangeArg | null; colors: SeriesStyle[] | null; hideLegend: boolean | null; + title: string | null; } type Output = EmbeddableExpression; @@ -61,9 +62,14 @@ export function savedVisualization(): ExpressionFunctionDefinition< help: argHelp.hideLegend, required: false, }, + title: { + types: ['string'], + help: argHelp.title, + required: false, + }, }, type: EmbeddableExpressionType, - fn: (input, { id, timerange, colors, hideLegend }) => { + fn: (input, { id, timerange, colors, hideLegend, title }) => { const filters = input ? input.and : []; const visOptions: VisualizeInput['vis'] = {}; @@ -90,6 +96,7 @@ export function savedVisualization(): ExpressionFunctionDefinition< timeRange: timerange || defaultTimeRange, filters: getQueryFilters(filters), vis: visOptions, + title: title === null ? undefined : title, }, embeddableType: EmbeddableTypes.visualization, generatedAt: Date.now(), diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/lens.test.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/lens.test.ts index 7bcfd6bef4620..0df39f281da9c 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/lens.test.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/lens.test.ts @@ -52,4 +52,16 @@ describe('toExpression', () => { expect(timerangeExpression.chain[0].arguments.from[0]).toEqual(input.timeRange?.from); expect(timerangeExpression.chain[0].arguments.to[0]).toEqual(input.timeRange?.to); }); + + it('includes empty panel title', () => { + const input: SavedLensInput = { + ...baseEmbeddableInput, + title: '', + }; + + const expression = toExpression(input); + const ast = fromExpression(expression); + + expect(ast.chain[0].arguments).toHaveProperty('title', [input.title]); + }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/lens.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/lens.ts index 5bb45c5ca129e..a8e200dd3e4ba 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/lens.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/lens.ts @@ -13,7 +13,7 @@ export function toExpression(input: SavedLensInput): string { expressionParts.push(`id="${input.id}"`); - if (input.title) { + if (input.title !== undefined) { expressionParts.push(`title="${input.title}"`); } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.test.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.test.ts index d910c734a6974..d2c803a1ff208 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.test.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.test.ts @@ -69,4 +69,16 @@ describe('toExpression', () => { expect(timerangeExpression.chain[0].arguments.from[0]).toEqual(input.timeRange?.from); expect(timerangeExpression.chain[0].arguments.to[0]).toEqual(input.timeRange?.to); }); + + it('includes empty panel title', () => { + const input: MapEmbeddableInput = { + ...baseSavedMapInput, + title: '', + }; + + const expression = toExpression(input); + const ast = fromExpression(expression); + + expect(ast.chain[0].arguments).toHaveProperty('title', [input.title]); + }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.ts index 111fdc71fa242..769c2c9e10e9c 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.ts @@ -12,7 +12,7 @@ export function toExpression(input: MapEmbeddableInput): string { expressionParts.push('savedMap'); expressionParts.push(`id="${input.id}"`); - if (input.title) { + if (input.title !== undefined) { expressionParts.push(`title="${input.title}"`); } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.test.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.test.ts index 07f828755e46f..4550a90ce98a2 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.test.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.test.ts @@ -69,4 +69,16 @@ describe('toExpression', () => { expect(aColor?.chain[0].arguments.color[0]).toBe(colorMap.a); expect(bColor?.chain[0].arguments.color[0]).toBe(colorMap.b); }); + + it('includes empty panel title', () => { + const input = { + ...baseInput, + title: '', + }; + + const expression = toExpression(input); + const ast = fromExpression(expression); + + expect(ast.chain[0].arguments).toHaveProperty('title', [input.title]); + }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.ts index f03c10e2d424e..a8adbf9d2d860 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.ts @@ -12,6 +12,10 @@ export function toExpression(input: VisualizeInput): string { expressionParts.push('savedVisualization'); expressionParts.push(`id="${input.id}"`); + if (input.title !== undefined) { + expressionParts.push(`title="${input.title}"`); + } + if (input.timeRange) { expressionParts.push( `timerange={timerange from="${input.timeRange.from}" to="${input.timeRange.to}"}` diff --git a/x-pack/plugins/canvas/i18n/functions/dict/saved_visualization.ts b/x-pack/plugins/canvas/i18n/functions/dict/saved_visualization.ts index e8cbddc5c1102..257e251fe2bc2 100644 --- a/x-pack/plugins/canvas/i18n/functions/dict/saved_visualization.ts +++ b/x-pack/plugins/canvas/i18n/functions/dict/saved_visualization.ts @@ -29,5 +29,8 @@ export const help: FunctionHelp> = { defaultMessage: `Specifies the option to hide the legend`, } ), + title: i18n.translate('xpack.canvas.functions.savedVisualization.args.titleHelpText', { + defaultMessage: `The title for the visualization object`, + }), }, }; diff --git a/x-pack/plugins/canvas/public/components/datatable/datatable.scss b/x-pack/plugins/canvas/public/components/datatable/datatable.scss index bd11bff18e091..8e36de3b84423 100644 --- a/x-pack/plugins/canvas/public/components/datatable/datatable.scss +++ b/x-pack/plugins/canvas/public/components/datatable/datatable.scss @@ -4,7 +4,6 @@ display: flex; flex-direction: column; justify-content: space-between; - font-size: $euiFontSizeS; .canvasDataTable__tableWrapper { @include euiScrollBar; @@ -33,7 +32,6 @@ .canvasDataTable__th, .canvasDataTable__td { - text-align: left; padding: $euiSizeS $euiSizeXS; border-bottom: $euiBorderThin; } diff --git a/x-pack/plugins/canvas/public/components/expression_input/__stories__/__snapshots__/expression_input.stories.storyshot b/x-pack/plugins/canvas/public/components/expression_input/__stories__/__snapshots__/expression_input.stories.storyshot index 99d5dc3c115be..5c17eb2b68137 100644 --- a/x-pack/plugins/canvas/public/components/expression_input/__stories__/__snapshots__/expression_input.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/expression_input/__stories__/__snapshots__/expression_input.stories.storyshot @@ -16,18 +16,7 @@ exports[`Storyshots components/ExpressionInput default 1`] = ` id="generated-id" onBlur={[Function]} onFocus={[Function]} - > -
-
-
+ />
diff --git a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts index f7ae9fc6d0f91..c8fe72e6f2c1e 100644 --- a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts +++ b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts @@ -13,6 +13,7 @@ import { SearchInterceptorDeps, UI_SETTINGS, } from '../../../../../src/plugins/data/public'; +import { isErrorResponse, isCompleteResponse } from '../../../../../src/plugins/data/public'; import { AbortError, toPromise } from '../../../../../src/plugins/data/common'; import { IAsyncSearchOptions } from '.'; import { IAsyncSearchRequest, ENHANCED_ES_SEARCH_STRATEGY } from '../../common'; @@ -66,12 +67,12 @@ export class EnhancedSearchInterceptor extends SearchInterceptor { return this.runSearch(request, combinedSignal, strategy).pipe( expand((response) => { // If the response indicates of an error, stop polling and complete the observable - if (!response || (!response.isRunning && response.isPartial)) { + if (isErrorResponse(response)) { return throwError(new AbortError()); } // If the response indicates it is complete, stop polling and complete the observable - if (!response.isRunning) { + if (isCompleteResponse(response)) { return EMPTY; } diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts index 72ea1f096e8fb..f3cf67a487a68 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts @@ -19,7 +19,11 @@ import { shimHitsTotal, } from '../../../../../src/plugins/data/server'; import { IEnhancedEsSearchRequest } from '../../common'; -import { ISearchOptions, IEsSearchResponse } from '../../../../../src/plugins/data/common/search'; +import { + ISearchOptions, + IEsSearchResponse, + isCompleteResponse, +} from '../../../../../src/plugins/data/common/search'; function isEnhancedEsSearchResponse(response: any): response is IEsSearchResponse { return response.hasOwnProperty('isPartial') && response.hasOwnProperty('isRunning'); @@ -48,8 +52,7 @@ export const enhancedEsSearchStrategyProvider = ( usage && isAsync && isEnhancedEsSearchResponse(response) && - !response.isRunning && - !response.isPartial + isCompleteResponse(response) ) { usage.trackSuccess(response.rawResponse.took); } diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/README.md b/x-pack/plugins/drilldowns/url_drilldown/README.md similarity index 65% rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/README.md rename to x-pack/plugins/drilldowns/url_drilldown/README.md index 996723ccb914d..8eedc44ca35ae 100644 --- a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/README.md +++ b/x-pack/plugins/drilldowns/url_drilldown/README.md @@ -1,24 +1,26 @@ -# Basic url drilldown implementation +## URL drilldown + +> NOTE: This plugin contains implementation of URL drilldown. For drilldowns infrastructure code refer to `ui_actions_enhanced` plugin. Url drilldown allows navigating to external URL or to internal kibana URL. By using variables in url template result url can be dynamic and depend on user's interaction. URL drilldown has 3 sources for variables: -- Global static variables like, for example, `kibanaUrl`. Such variables won’t change depending on a place where url drilldown is used. -- Context variables are dynamic and different depending on where drilldown is created and used. -- Event variables depend on a trigger context. These variables are dynamically extracted from the action context when drilldown is executed. +1. Global static variables like, for example, `kibanaUrl`. Such variables won’t change depending on a place where url drilldown is used. +2. Context variables are dynamic and different depending on where drilldown is created and used. +3. Event variables depend on a trigger context. These variables are dynamically extracted from the action context when drilldown is executed. Difference between `event` and `context` variables, is that real `context` variables are available during drilldown creation (e.g. embeddable panel), but `event` variables mapped from trigger context. Since there is no trigger context during drilldown creation, we have to provide some _mock_ variables for validating and previewing the URL. In current implementation url drilldown has to be used inside the embeddable and with `ValueClickTrigger` or `RangeSelectTrigger`. -- `context` variables extracted from `embeddable` -- `event` variables extracted from `trigger` context +* `context` variables extracted from `embeddable` +* `event` variables extracted from `trigger` context In future this basic url drilldown implementation would allow injecting more variables into `context` (e.g. `dashboard` app specific variables) and would allow providing support for new trigger types from outside. This extensibility improvements are tracked here: https://github.com/elastic/kibana/issues/55324 In case a solution app has a use case for url drilldown that has to be different from current basic implementation and -just extending variables list is not enough, then recommendation is to create own custom url drilldown and reuse building blocks from `ui_actions_enhanced`. +just extending variables list is not enough, then recommendation is to create own custom url drilldown and reuse building blocks from `ui_actions_enhanced`. \ No newline at end of file diff --git a/x-pack/plugins/drilldowns/url_drilldown/kibana.json b/x-pack/plugins/drilldowns/url_drilldown/kibana.json new file mode 100644 index 0000000000000..9bdd13fbfea26 --- /dev/null +++ b/x-pack/plugins/drilldowns/url_drilldown/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "urlDrilldown", + "version": "kibana", + "server": false, + "ui": true, + "requiredPlugins": ["embeddable", "uiActions", "uiActionsEnhanced"], + "requiredBundles": ["kibanaUtils", "kibanaReact"] +} diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/index.ts b/x-pack/plugins/drilldowns/url_drilldown/public/index.ts new file mode 100644 index 0000000000000..b040ef625bc1f --- /dev/null +++ b/x-pack/plugins/drilldowns/url_drilldown/public/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext } from 'src/core/public'; +import { UrlDrilldownPlugin } from './plugin'; + +export function plugin(context: PluginInitializerContext) { + return new UrlDrilldownPlugin(context); +} diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/i18n.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/i18n.ts similarity index 62% rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/i18n.ts rename to x-pack/plugins/drilldowns/url_drilldown/public/lib/i18n.ts index 748f6f4cecedd..7e91c6b849035 100644 --- a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/i18n.ts +++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/i18n.ts @@ -6,9 +6,6 @@ import { i18n } from '@kbn/i18n'; -export const txtUrlDrilldownDisplayName = i18n.translate( - 'xpack.embeddableEnhanced.drilldowns.urlDrilldownDisplayName', - { - defaultMessage: 'Go to URL', - } -); +export const txtUrlDrilldownDisplayName = i18n.translate('xpack.urlDrilldown.DisplayName', { + defaultMessage: 'Go to URL', +}); diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/index.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/index.ts similarity index 100% rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/index.ts rename to x-pack/plugins/drilldowns/url_drilldown/public/lib/index.ts diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown.test.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.test.ts similarity index 100% rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown.test.ts rename to x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.test.ts diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown.tsx b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx similarity index 100% rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown.tsx rename to x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown_scope.test.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.test.ts similarity index 100% rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown_scope.test.ts rename to x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.test.ts diff --git a/x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown_scope.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts similarity index 100% rename from x-pack/plugins/embeddable_enhanced/public/drilldowns/url_drilldown/url_drilldown_scope.ts rename to x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/plugin.ts b/x-pack/plugins/drilldowns/url_drilldown/public/plugin.ts new file mode 100644 index 0000000000000..82ce7a129f497 --- /dev/null +++ b/x-pack/plugins/drilldowns/url_drilldown/public/plugin.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public'; +import { EmbeddableSetup, EmbeddableStart } from '../../../../../src/plugins/embeddable/public'; +import { + AdvancedUiActionsSetup, + AdvancedUiActionsStart, + urlDrilldownGlobalScopeProvider, +} from '../../../ui_actions_enhanced/public'; +import { UrlDrilldown } from './lib'; +import { createStartServicesGetter } from '../../../../../src/plugins/kibana_utils/public'; + +export interface SetupDependencies { + embeddable: EmbeddableSetup; + uiActionsEnhanced: AdvancedUiActionsSetup; +} + +export interface StartDependencies { + embeddable: EmbeddableStart; + uiActionsEnhanced: AdvancedUiActionsStart; +} + +// eslint-disable-next-line +export interface SetupContract {} + +// eslint-disable-next-line +export interface StartContract {} + +export class UrlDrilldownPlugin + implements Plugin { + constructor(protected readonly context: PluginInitializerContext) {} + + public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract { + const startServices = createStartServicesGetter(core.getStartServices); + plugins.uiActionsEnhanced.registerDrilldown( + new UrlDrilldown({ + getGlobalScope: urlDrilldownGlobalScopeProvider({ core }), + navigateToUrl: (url: string) => + core.getStartServices().then(([{ application }]) => application.navigateToUrl(url)), + getSyntaxHelpDocsLink: () => + startServices().core.docLinks.links.dashboard.urlDrilldownTemplateSyntax, + getVariablesHelpDocsLink: () => + startServices().core.docLinks.links.dashboard.urlDrilldownVariables, + }) + ); + + return {}; + } + + public start(core: CoreStart, plugins: StartDependencies): StartContract { + return {}; + } + + public stop() {} +} diff --git a/x-pack/plugins/embeddable_enhanced/kibana.json b/x-pack/plugins/embeddable_enhanced/kibana.json index acada946fe0d1..8d49e3e26eb7b 100644 --- a/x-pack/plugins/embeddable_enhanced/kibana.json +++ b/x-pack/plugins/embeddable_enhanced/kibana.json @@ -3,6 +3,5 @@ "version": "kibana", "server": false, "ui": true, - "requiredPlugins": ["embeddable", "kibanaReact", "uiActions", "uiActionsEnhanced"], - "requiredBundles": ["kibanaUtils"] + "requiredPlugins": ["embeddable", "kibanaReact", "uiActions", "uiActionsEnhanced"] } diff --git a/x-pack/plugins/embeddable_enhanced/public/plugin.ts b/x-pack/plugins/embeddable_enhanced/public/plugin.ts index 2138a372523b7..5d5ad852839d4 100644 --- a/x-pack/plugins/embeddable_enhanced/public/plugin.ts +++ b/x-pack/plugins/embeddable_enhanced/public/plugin.ts @@ -28,11 +28,8 @@ import { UiActionsEnhancedDynamicActionManager as DynamicActionManager, AdvancedUiActionsSetup, AdvancedUiActionsStart, - urlDrilldownGlobalScopeProvider, } from '../../ui_actions_enhanced/public'; import { PanelNotificationsAction, ACTION_PANEL_NOTIFICATIONS } from './actions'; -import { UrlDrilldown } from './drilldowns'; -import { createStartServicesGetter } from '../../../../src/plugins/kibana_utils/public'; declare module '../../../../src/plugins/ui_actions/public' { export interface ActionContextMapping { @@ -64,23 +61,10 @@ export class EmbeddableEnhancedPlugin public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract { this.setCustomEmbeddableFactoryProvider(plugins); - const startServices = createStartServicesGetter(core.getStartServices); const panelNotificationAction = new PanelNotificationsAction(); plugins.uiActionsEnhanced.registerAction(panelNotificationAction); plugins.uiActionsEnhanced.attachAction(PANEL_NOTIFICATION_TRIGGER, panelNotificationAction.id); - plugins.uiActionsEnhanced.registerDrilldown( - new UrlDrilldown({ - getGlobalScope: urlDrilldownGlobalScopeProvider({ core }), - navigateToUrl: (url: string) => - core.getStartServices().then(([{ application }]) => application.navigateToUrl(url)), - getSyntaxHelpDocsLink: () => - startServices().core.docLinks.links.dashboard.urlDrilldownTemplateSyntax, - getVariablesHelpDocsLink: () => - startServices().core.docLinks.links.dashboard.urlDrilldownVariables, - }) - ); - return {}; } diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/flash_messages_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/flash_messages_logic.mock.ts new file mode 100644 index 0000000000000..a610ea0238ac0 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/flash_messages_logic.mock.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const mockFlashMessagesValues = { + messages: [], + queuedMessages: [], +}; + +export const mockFlashMessagesActions = { + setFlashMessages: jest.fn(), + clearFlashMessages: jest.fn(), + setQueuedMessages: jest.fn(), + clearQueuedMessages: jest.fn(), +}; diff --git a/x-pack/legacy/common/poller.d.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/http_logic.mock.ts similarity index 56% rename from x-pack/legacy/common/poller.d.ts rename to x-pack/plugins/enterprise_search/public/applications/__mocks__/http_logic.mock.ts index df39d93a28a81..e77863c70c23a 100644 --- a/x-pack/legacy/common/poller.d.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/http_logic.mock.ts @@ -4,11 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -export declare class Poller { - constructor(options: any); +import { httpServiceMock } from 'src/core/public/mocks'; - public start(): void; - public stop(): void; - public isRunning(): boolean; - public getPollFrequency(): number; -} +export const mockHttpValues = { + http: httpServiceMock.createSetupContract(), + errorConnecting: false, + readOnlyMode: false, +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/index.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/index.ts index e999d40a3f8e6..88a900f69c5ec 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/index.ts @@ -6,7 +6,11 @@ export { mockHistory, mockLocation } from './react_router_history.mock'; export { mockKibanaContext } from './kibana_context.mock'; -export { mockLicenseContext } from './license_context.mock'; +export { mockLicensingValues } from './licensing_logic.mock'; +export { mockHttpValues } from './http_logic.mock'; +export { mockFlashMessagesValues, mockFlashMessagesActions } from './flash_messages_logic.mock'; +export { mockAllValues, mockAllActions, setMockValues } from './kea.mock'; + export { mountWithContext, mountWithKibanaContext, diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts index 5049e9da21ce9..bad6beaa1652e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts @@ -4,21 +4,50 @@ * you may not use this file except in compliance with the Elastic License. */ +/** + * Combine all shared mock values/actions into a single obj + * + * NOTE: These variable names MUST start with 'mock*' in order for + * Jest to accept its use within a jest.mock() + */ +import { mockLicensingValues } from './licensing_logic.mock'; +import { mockHttpValues } from './http_logic.mock'; +import { mockFlashMessagesValues, mockFlashMessagesActions } from './flash_messages_logic.mock'; + +export const mockAllValues = { + ...mockLicensingValues, + ...mockHttpValues, + ...mockFlashMessagesValues, +}; +export const mockAllActions = { + ...mockFlashMessagesActions, +}; + +/** + * Import this file directly to mock useValues with a set of default values for all shared logic files. + * Example usage: + * + * import '../../../__mocks__/kea'; // Must come before kea's import, adjust relative path as needed + */ jest.mock('kea', () => ({ ...(jest.requireActual('kea') as object), - useValues: jest.fn(() => ({})), - useActions: jest.fn(() => ({})), + useValues: jest.fn(() => ({ ...mockAllValues })), + useActions: jest.fn(() => ({ ...mockAllActions })), })); /** + * Call this function to override a specific set of Kea values while retaining all other defaults * Example usage within a component test: * - * import '../../../__mocks__/kea'; // Must come before kea's import, adjust relative path as needed - * - * import { useActions, useValues } from 'kea'; + * import '../../../__mocks__/kea'; + * import { setMockValues } from ''../../../__mocks__'; * * it('some test', () => { - * (useValues as jest.Mock).mockImplementationOnce(() => ({ someValue: 'hello' })); - * (useActions as jest.Mock).mockImplementationOnce(() => ({ someAction: () => 'world' })); + * setMockValues({ someValue: 'hello' }); * }); */ +import { useValues } from 'kea'; + +export const setMockValues = (values: object) => { + (useValues as jest.Mock).mockImplementation(() => ({ ...mockAllValues, ...values })); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_context.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_context.mock.ts index 890072ab42eb9..ea3c3923cc472 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_context.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_context.mock.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { httpServiceMock } from 'src/core/public/mocks'; import { ExternalUrl } from '../shared/enterprise_search_url'; /** @@ -12,7 +11,6 @@ import { ExternalUrl } from '../shared/enterprise_search_url'; * @see enterprise_search/public/index.tsx for the KibanaContext definition/import */ export const mockKibanaContext = { - http: httpServiceMock.createSetupContract(), navigateToUrl: jest.fn(), setBreadcrumbs: jest.fn(), setDocTitle: jest.fn(), diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/license_context.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/licensing_logic.mock.ts similarity index 79% rename from x-pack/plugins/enterprise_search/public/applications/__mocks__/license_context.mock.ts rename to x-pack/plugins/enterprise_search/public/applications/__mocks__/licensing_logic.mock.ts index 7c37ecc7cde1b..51b32e7a877b2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/license_context.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/licensing_logic.mock.ts @@ -6,6 +6,8 @@ import { licensingMock } from '../../../../licensing/public/mocks'; -export const mockLicenseContext = { +export const mockLicensingValues = { license: licensingMock.createLicense(), + hasPlatinumLicense: false, + hasGoldLicense: false, }; diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_with_context.mock.tsx b/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_with_context.mock.tsx index 826e0482acef7..646c3104c286f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_with_context.mock.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_with_context.mock.tsx @@ -15,8 +15,6 @@ import { getContext, resetContext } from 'kea'; import { I18nProvider } from '@kbn/i18n/react'; import { KibanaContext } from '../'; import { mockKibanaContext } from './kibana_context.mock'; -import { LicenseContext } from '../shared/licensing'; -import { mockLicenseContext } from './license_context.mock'; /** * This helper mounts a component with all the contexts/providers used @@ -34,9 +32,7 @@ export const mountWithContext = (children: React.ReactNode, context?: object) => return mount( - - {children} - + {children} ); @@ -67,7 +63,7 @@ export const mountWithKibanaContext = (children: React.ReactNode, context?: obje */ export const mountWithAsyncContext = async ( children: React.ReactNode, - context: object + context?: object ): Promise => { let wrapper: ReactWrapper | undefined; diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/react_router_history.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/react_router_history.mock.ts index 842dcefd3aef8..7b3ac86ad0ab1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/react_router_history.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/react_router_history.mock.ts @@ -14,6 +14,7 @@ export const mockHistory = { location: { pathname: '/current-path', }, + listen: jest.fn(() => jest.fn()), }; export const mockLocation = { key: 'someKey', diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_usecontext.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_usecontext.mock.ts index 3a2193db646de..df9e58994e36b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_usecontext.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_usecontext.mock.ts @@ -9,11 +9,10 @@ * Jest to accept its use within a jest.mock() */ import { mockKibanaContext } from './kibana_context.mock'; -import { mockLicenseContext } from './license_context.mock'; jest.mock('react', () => ({ ...(jest.requireActual('react') as object), - useContext: jest.fn(() => ({ ...mockKibanaContext, ...mockLicenseContext })), + useContext: jest.fn(() => ({ ...mockKibanaContext })), useEffect: jest.fn((fn) => fn()), // Calls on mount/every update - use mount for more complex behavior })); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts index 0f7bfe09edf7e..9410b9ef7cb03 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts @@ -56,6 +56,15 @@ describe('AppLogic', () => { }), }); }); + + it('gracefully handles missing initial data', () => { + AppLogic.actions.initializeAppData({}); + + expect(AppLogic.values).toEqual({ + ...DEFAULT_VALUES, + hasInitialized: true, + }); + }); }); describe('setOnboardingComplete()', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts index 8e5a8d75f407f..932e84af45c2b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts @@ -39,7 +39,7 @@ export const AppLogic = kea>({ account: [ {}, { - initializeAppData: (_, { appSearch: account }) => account, + initializeAppData: (_, { appSearch: account }) => account || {}, setOnboardingComplete: (account) => ({ ...account, onboardingComplete: true, @@ -49,7 +49,7 @@ export const AppLogic = kea>({ configuredLimits: [ {}, { - initializeAppData: (_, { configuredLimits }) => configuredLimits.appSearch, + initializeAppData: (_, { configuredLimits }) => configuredLimits?.appSearch || {}, }, ], ilmEnabled: [ diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/empty_state.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/empty_state.test.tsx index 7e6876bc9b3a4..233db7d4c5917 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/empty_state.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/empty_state.test.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import '../../../../__mocks__/kea.mock'; import '../../../../__mocks__/shallow_usecontext.mock'; import React from 'react'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/empty_state.tsx index 58691cf09b4a5..5ed1f0b277306 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/empty_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/empty_state.tsx @@ -5,10 +5,12 @@ */ import React, { useContext } from 'react'; +import { useValues } from 'kea'; import { EuiPageContent, EuiEmptyPrompt, EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { sendTelemetry } from '../../../../shared/telemetry'; +import { HttpLogic } from '../../../../shared/http'; import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome'; import { KibanaContext, IKibanaContext } from '../../../../index'; import { CREATE_ENGINES_PATH } from '../../../routes'; @@ -18,9 +20,9 @@ import { EngineOverviewHeader } from './header'; import './empty_state.scss'; export const EmptyState: React.FC = () => { + const { http } = useValues(HttpLogic); const { externalUrl: { getAppSearchUrl }, - http, } = useContext(KibanaContext) as IKibanaContext; const buttonProps = { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/header.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/header.test.tsx index 7f22ce132d405..8c7dfa2b7c3d6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/header.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/header.test.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import '../../../../__mocks__/kea.mock'; import '../../../../__mocks__/shallow_usecontext.mock'; import React from 'react'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/header.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/header.tsx index 1a1ae295d4828..dca0d45a207b4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/header.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/header.tsx @@ -5,6 +5,7 @@ */ import React, { useContext } from 'react'; +import { useValues } from 'kea'; import { EuiPageHeader, EuiPageHeaderSection, @@ -16,12 +17,13 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import { sendTelemetry } from '../../../../shared/telemetry'; +import { HttpLogic } from '../../../../shared/http'; import { KibanaContext, IKibanaContext } from '../../../../index'; export const EngineOverviewHeader: React.FC = () => { + const { http } = useValues(HttpLogic); const { externalUrl: { getAppSearchUrl }, - http, } = useContext(KibanaContext) as IKibanaContext; const buttonProps = { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx index c2379fb33bd71..44afce96c1a6c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx @@ -4,13 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ +import '../../../__mocks__/kea.mock'; import '../../../__mocks__/react_router_history.mock'; import React from 'react'; import { act } from 'react-dom/test-utils'; import { shallow, ReactWrapper } from 'enzyme'; -import { mountWithAsyncContext, mockKibanaContext } from '../../../__mocks__'; +import { mountWithAsyncContext, mockHttpValues, setMockValues } from '../../../__mocks__'; import { LoadingState, EmptyState } from './components'; import { EngineTable } from './engine_table'; @@ -18,8 +19,6 @@ import { EngineTable } from './engine_table'; import { EngineOverview } from './'; describe('EngineOverview', () => { - const mockHttp = mockKibanaContext.http; - describe('non-happy-path states', () => { it('isLoading', () => { const wrapper = shallow(); @@ -28,15 +27,16 @@ describe('EngineOverview', () => { }); it('isEmpty', async () => { - const wrapper = await mountWithAsyncContext(, { + setMockValues({ http: { - ...mockHttp, + ...mockHttpValues.http, get: () => ({ results: [], meta: { page: { total_results: 0 } }, }), }, }); + const wrapper = await mountWithAsyncContext(); expect(wrapper.find(EmptyState)).toHaveLength(1); }); @@ -65,12 +65,11 @@ describe('EngineOverview', () => { beforeEach(() => { jest.clearAllMocks(); + setMockValues({ http: { ...mockHttpValues.http, get: mockApi } }); }); it('renders and calls the engines API', async () => { - const wrapper = await mountWithAsyncContext(, { - http: { ...mockHttp, get: mockApi }, - }); + const wrapper = await mountWithAsyncContext(); expect(wrapper.find(EngineTable)).toHaveLength(1); expect(mockApi).toHaveBeenNthCalledWith(1, '/api/app_search/engines', { @@ -83,10 +82,11 @@ describe('EngineOverview', () => { describe('when on a platinum license', () => { it('renders a 2nd meta engines table & makes a 2nd meta engines API call', async () => { - const wrapper = await mountWithAsyncContext(, { - http: { ...mockHttp, get: mockApi }, - license: { type: 'platinum', isActive: true }, + setMockValues({ + hasPlatinumLicense: true, + http: { ...mockHttpValues.http, get: mockApi }, }); + const wrapper = await mountWithAsyncContext(); expect(wrapper.find(EngineTable)).toHaveLength(2); expect(mockApi).toHaveBeenNthCalledWith(2, '/api/app_search/engines', { @@ -103,9 +103,7 @@ describe('EngineOverview', () => { wrapper.find(EngineTable).prop('pagination'); it('passes down page data from the API', async () => { - const wrapper = await mountWithAsyncContext(, { - http: { ...mockHttp, get: mockApi }, - }); + const wrapper = await mountWithAsyncContext(); const pagination = getTablePagination(wrapper); expect(pagination.totalEngines).toEqual(100); @@ -113,9 +111,7 @@ describe('EngineOverview', () => { }); it('re-polls the API on page change', async () => { - const wrapper = await mountWithAsyncContext(, { - http: { ...mockHttp, get: mockApi }, - }); + const wrapper = await mountWithAsyncContext(); await act(async () => getTablePagination(wrapper).onPaginate(5)); wrapper.update(); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx index 9703fde7e140a..0cb9ba106dbb8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; +import { useValues } from 'kea'; import { EuiPageContent, EuiPageContentHeader, @@ -12,13 +13,13 @@ import { EuiTitle, EuiSpacer, } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; import { SendAppSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; import { FlashMessages } from '../../../shared/flash_messages'; -import { LicenseContext, ILicenseContext, hasPlatinumLicense } from '../../../shared/licensing'; -import { KibanaContext, IKibanaContext } from '../../../index'; +import { HttpLogic } from '../../../shared/http'; +import { LicensingLogic } from '../../../shared/licensing'; import { EngineIcon } from './assets/engine_icon'; import { MetaEngineIcon } from './assets/meta_engine_icon'; @@ -38,8 +39,8 @@ interface ISetEnginesCallbacks { } export const EngineOverview: React.FC = () => { - const { http } = useContext(KibanaContext) as IKibanaContext; - const { license } = useContext(LicenseContext) as ILicenseContext; + const { http } = useValues(HttpLogic); + const { hasPlatinumLicense } = useValues(LicensingLogic); const [isLoading, setIsLoading] = useState(true); const [engines, setEngines] = useState([]); @@ -71,13 +72,13 @@ export const EngineOverview: React.FC = () => { }, [enginesPage]); useEffect(() => { - if (hasPlatinumLicense(license)) { + if (hasPlatinumLicense) { const params = { type: 'meta', pageIndex: metaEnginesPage }; const callbacks = { setResults: setMetaEngines, setResultsTotal: setMetaEnginesTotal }; setEnginesData(params, callbacks); } - }, [license, metaEnginesPage]); + }, [hasPlatinumLicense, metaEnginesPage]); if (isLoading) return ; if (!engines.length) return ; @@ -94,10 +95,9 @@ export const EngineOverview: React.FC = () => {

- + {i18n.translate('xpack.enterpriseSearch.appSearch.enginesOverview.engines', { + defaultMessage: 'Engines', + })}

@@ -119,10 +119,9 @@ export const EngineOverview: React.FC = () => {

- + {i18n.translate('xpack.enterpriseSearch.appSearch.enginesOverview.metaEngines', { + defaultMessage: 'Meta Engines', + })}

diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.test.tsx index 46b6e61e352de..8e92f21f8ffed 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.test.tsx @@ -4,10 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ +import '../../../__mocks__/kea.mock'; +import '../../../__mocks__/shallow_usecontext.mock'; +import { mockHttpValues } from '../../../__mocks__/'; + import React from 'react'; +import { mount } from 'enzyme'; +import { I18nProvider } from '@kbn/i18n/react'; import { EuiBasicTable, EuiPagination, EuiButtonEmpty, EuiLink } from '@elastic/eui'; -import { mountWithContext } from '../../../__mocks__'; jest.mock('../../../shared/telemetry', () => ({ sendTelemetry: jest.fn() })); import { sendTelemetry } from '../../../shared/telemetry'; @@ -16,22 +21,24 @@ import { EngineTable } from './engine_table'; describe('EngineTable', () => { const onPaginate = jest.fn(); // onPaginate updates the engines API call upstream - const wrapper = mountWithContext( - + const wrapper = mount( + + + ); const table = wrapper.find(EuiBasicTable); @@ -56,7 +63,7 @@ describe('EngineTable', () => { link.simulate('click'); expect(sendTelemetry).toHaveBeenCalledWith({ - http: expect.any(Object), + http: mockHttpValues.http, product: 'app_search', action: 'clicked', metric: 'engine_table_link', @@ -71,10 +78,16 @@ describe('EngineTable', () => { }); it('handles empty data', () => { - const emptyWrapper = mountWithContext( - {} }} /> + const emptyWrapper = mount( + + {} }} + /> + ); const emptyTable = emptyWrapper.find(EuiBasicTable); + expect(emptyTable.prop('pagination').pageIndex).toEqual(0); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.tsx index 9c6122c88c7d7..6888be1dc2b5b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.tsx @@ -5,11 +5,13 @@ */ import React, { useContext } from 'react'; +import { useValues } from 'kea'; import { EuiBasicTable, EuiBasicTableColumn, EuiLink } from '@elastic/eui'; import { FormattedMessage, FormattedDate, FormattedNumber } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { sendTelemetry } from '../../../shared/telemetry'; +import { HttpLogic } from '../../../shared/http'; import { KibanaContext, IKibanaContext } from '../../../index'; import { getEngineRoute } from '../../routes'; @@ -40,9 +42,9 @@ export const EngineTable: React.FC = ({ data, pagination: { totalEngines, pageIndex, onPaginate }, }) => { + const { http } = useValues(HttpLogic); const { externalUrl: { getAppSearchUrl }, - http, } = useContext(KibanaContext) as IKibanaContext; const engineLinkProps = (name: string) => ({ diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.test.tsx new file mode 100644 index 0000000000000..8d48875a8e1f5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.test.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { ErrorStatePrompt } from '../../../shared/error_state'; +import { ErrorConnecting } from './'; + +describe('ErrorConnecting', () => { + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(ErrorStatePrompt)).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.tsx new file mode 100644 index 0000000000000..567c77792583d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiPage, EuiPageContent } from '@elastic/eui'; + +import { ErrorStatePrompt } from '../../../shared/error_state'; + +export const ErrorConnecting: React.FC = () => ( + + + + + +); diff --git a/x-pack/legacy/server/lib/create_router/is_es_error_factory/index.js b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/index.ts similarity index 80% rename from x-pack/legacy/server/lib/create_router/is_es_error_factory/index.js rename to x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/index.ts index 441648a8701e0..c8b71e1a6e791 100644 --- a/x-pack/legacy/server/lib/create_router/is_es_error_factory/index.js +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { isEsErrorFactory } from './is_es_error_factory'; +export { ErrorConnecting } from './error_connecting'; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.test.tsx index a76b654ccddd0..35301af44b413 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.test.tsx @@ -4,7 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import '../../../__mocks__/kea.mock'; +import '../../../__mocks__/shallow_usecontext.mock'; + +import React, { useContext } from 'react'; import { shallow } from 'enzyme'; import { EuiCard } from '@elastic/eui'; @@ -24,6 +27,7 @@ describe('ProductCard', () => { }); it('renders an App Search card', () => { + (useContext as jest.Mock).mockImplementationOnce(() => ({ config: { host: 'localhost' } })); const wrapper = shallow(); const card = wrapper.find(EuiCard).dive().shallow(); @@ -32,13 +36,14 @@ describe('ProductCard', () => { const button = card.find(EuiButton); expect(button.prop('to')).toEqual('/app/enterprise_search/app_search'); - expect(button.prop('data-test-subj')).toEqual('LaunchAppSearchButton'); + expect(button.prop('children')).toEqual('Launch App Search'); button.simulate('click'); expect(sendTelemetry).toHaveBeenCalledWith(expect.objectContaining({ metric: 'app_search' })); }); it('renders a Workplace Search card', () => { + (useContext as jest.Mock).mockImplementationOnce(() => ({ config: { host: 'localhost' } })); const wrapper = shallow(); const card = wrapper.find(EuiCard).dive().shallow(); @@ -47,11 +52,21 @@ describe('ProductCard', () => { const button = card.find(EuiButton); expect(button.prop('to')).toEqual('/app/enterprise_search/workplace_search'); - expect(button.prop('data-test-subj')).toEqual('LaunchWorkplaceSearchButton'); + expect(button.prop('children')).toEqual('Launch Workplace Search'); button.simulate('click'); expect(sendTelemetry).toHaveBeenCalledWith( expect.objectContaining({ metric: 'workplace_search' }) ); }); + + it('renders correct button text when host not present', () => { + (useContext as jest.Mock).mockImplementation(() => ({ config: { host: '' } })); + + const wrapper = shallow(); + const card = wrapper.find(EuiCard).dive().shallow(); + const button = card.find(EuiButton); + + expect(button.prop('children')).toEqual('Setup Workplace Search'); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.tsx index 334ca126cabb9..482d68736af01 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.tsx @@ -5,14 +5,16 @@ */ import React, { useContext } from 'react'; -import upperFirst from 'lodash/upperFirst'; -import snakeCase from 'lodash/snakeCase'; +import { useValues } from 'kea'; +import { snakeCase } from 'lodash'; import { i18n } from '@kbn/i18n'; import { EuiCard, EuiTextColor } from '@elastic/eui'; +import { KibanaContext, IKibanaContext } from '../../../index'; + import { EuiButton } from '../../../shared/react_router_helpers'; import { sendTelemetry } from '../../../shared/telemetry'; -import { KibanaContext, IKibanaContext } from '../../../index'; +import { HttpLogic } from '../../../shared/http'; import './product_card.scss'; @@ -28,7 +30,26 @@ interface IProductCard { } export const ProductCard: React.FC = ({ product, image }) => { - const { http } = useContext(KibanaContext) as IKibanaContext; + const { http } = useValues(HttpLogic); + const { + config: { host }, + } = useContext(KibanaContext) as IKibanaContext; + + const LAUNCH_BUTTON_TEXT = i18n.translate( + 'xpack.enterpriseSearch.overview.productCard.launchButton', + { + defaultMessage: 'Launch {productName}', + values: { productName: product.NAME }, + } + ); + + const SETUP_BUTTON_TEXT = i18n.translate( + 'xpack.enterpriseSearch.overview.productCard.setupButton', + { + defaultMessage: 'Setup {productName}', + values: { productName: product.NAME }, + } + ); return ( = ({ product, image }) => { metric: snakeCase(product.ID), }) } - data-test-subj={`Launch${upperFirst(product.ID)}Button`} > - {i18n.translate('xpack.enterpriseSearch.overview.productCard.button', { - defaultMessage: `Launch {productName}`, - values: { productName: product.NAME }, - })} + {host ? LAUNCH_BUTTON_TEXT : SETUP_BUTTON_TEXT} } /> diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/index.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/index.ts new file mode 100644 index 0000000000000..b67d130cd68f0 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { ProductSelector } from './product_selector'; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.test.tsx new file mode 100644 index 0000000000000..44efa57db897f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.test.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import '../../../__mocks__/shallow_usecontext.mock'; + +import React, { useContext } from 'react'; +import { shallow } from 'enzyme'; +import { EuiPage } from '@elastic/eui'; + +import { ProductSelector } from './'; +import { ProductCard } from '../product_card'; + +describe('ProductSelector', () => { + it('renders the overview page and product cards with no host set', () => { + (useContext as jest.Mock).mockImplementationOnce(() => ({ config: { host: '' } })); + const wrapper = shallow(); + + expect(wrapper.find(EuiPage).hasClass('enterpriseSearchOverview')).toBe(true); + expect(wrapper.find(ProductCard)).toHaveLength(2); + }); + + describe('access checks when host is set', () => { + beforeEach(() => { + (useContext as jest.Mock).mockImplementationOnce(() => ({ config: { host: 'localhost' } })); + }); + + it('does not render the App Search card if the user does not have access to AS', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find(ProductCard)).toHaveLength(1); + expect(wrapper.find(ProductCard).prop('product').ID).toEqual('workplaceSearch'); + }); + + it('does not render the Workplace Search card if the user does not have access to WS', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find(ProductCard)).toHaveLength(1); + expect(wrapper.find(ProductCard).prop('product').ID).toEqual('appSearch'); + }); + + it('does not render any cards if the user does not have access', () => { + const wrapper = shallow(); + + expect(wrapper.find(ProductCard)).toHaveLength(0); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx new file mode 100644 index 0000000000000..07b8d4b9926d7 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useContext } from 'react'; + +import { + EuiPage, + EuiPageBody, + EuiPageHeader, + EuiPageHeaderSection, + EuiPageContentBody, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { KibanaContext, IKibanaContext } from '../../../index'; + +import { APP_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN } from '../../../../../common/constants'; + +import { SetEnterpriseSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; +import { SendEnterpriseSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; + +import { ProductCard } from '../product_card'; + +import AppSearchImage from '../../assets/app_search.png'; +import WorkplaceSearchImage from '../../assets/workplace_search.png'; + +interface IProductSelectorProps { + access: { + hasAppSearchAccess?: boolean; + hasWorkplaceSearchAccess?: boolean; + }; +} + +export const ProductSelector: React.FC = ({ access }) => { + const { hasAppSearchAccess, hasWorkplaceSearchAccess } = access; + const { + config: { host }, + } = useContext(KibanaContext) as IKibanaContext; + + const shouldShowAppSearchCard = !host || hasAppSearchAccess; + const shouldShowWorkplaceSearchCard = !host || hasWorkplaceSearchAccess; + + return ( + + + + + + + + +

+ {i18n.translate('xpack.enterpriseSearch.overview.heading', { + defaultMessage: 'Welcome to Elastic Enterprise Search', + })} +

+
+ +

+ {i18n.translate('xpack.enterpriseSearch.overview.subheading', { + defaultMessage: 'Select a product to get started', + })} +

+
+
+
+ + + {shouldShowAppSearchCard && ( + + + + )} + {shouldShowWorkplaceSearchCard && ( + + + + )} + + + +
+
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/assets/getting_started.png b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/assets/getting_started.png new file mode 100644 index 0000000000000..f0fcb432c29e1 Binary files /dev/null and b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/assets/getting_started.png differ diff --git a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/index.js b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/index.ts similarity index 83% rename from x-pack/legacy/plugins/xpack_main/server/routes/api/v1/index.js rename to x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/index.ts index 80baf7bf1a64d..c367424d375f9 100644 --- a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/index.js +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { xpackInfoRoute } from './xpack_info'; +export { SetupGuide } from './setup_guide'; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide.test.tsx new file mode 100644 index 0000000000000..63b0cc5a56cd1 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide.test.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { SetEnterpriseSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; +import { SetupGuide as SetupGuideLayout } from '../../../shared/setup_guide'; +import { SetupGuide } from './'; + +describe('SetupGuide', () => { + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(SetupGuideLayout)).toHaveLength(1); + expect(wrapper.find(SetPageChrome)).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide.tsx new file mode 100644 index 0000000000000..fcb3b399c75b0 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide.tsx @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiSpacer, EuiTitle, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; + +import { ENTERPRISE_SEARCH_PLUGIN } from '../../../../../common/constants'; +import { SetupGuide as SetupGuideLayout } from '../../../shared/setup_guide'; +import { SetEnterpriseSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; +import { SendEnterpriseSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; +import GettingStarted from './assets/getting_started.png'; + +export const SetupGuide: React.FC = () => ( + + + + + + {i18n.translate('xpack.enterpriseSearch.enterpriseSearch.setupGuide.videoAlt', + + + +

+ +

+
+ + +

+ +

+
+
+); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.test.tsx index cd2a22a45bbb4..2c0902163e3d6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.test.tsx @@ -4,47 +4,41 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; -import { shallow } from 'enzyme'; +import '../__mocks__/shallow_usecontext.mock'; +import React, { useContext } from 'react'; +import { shallow } from 'enzyme'; import { EuiPage } from '@elastic/eui'; +import '../__mocks__/kea.mock'; +import { useValues } from 'kea'; + import { EnterpriseSearch } from './'; -import { ProductCard } from './components/product_card'; +import { SetupGuide } from './components/setup_guide'; +import { ErrorConnecting } from './components/error_connecting'; +import { ProductSelector } from './components/product_selector'; describe('EnterpriseSearch', () => { - it('renders the overview page and product cards', () => { - const wrapper = shallow( - - ); - - expect(wrapper.find(EuiPage).hasClass('enterpriseSearchOverview')).toBe(true); - expect(wrapper.find(ProductCard)).toHaveLength(2); + beforeEach(() => { + (useValues as jest.Mock).mockReturnValue({ errorConnecting: false }); + (useContext as jest.Mock).mockImplementationOnce(() => ({ config: { host: 'localhost' } })); }); - describe('access checks', () => { - it('does not render the App Search card if the user does not have access to AS', () => { - const wrapper = shallow( - - ); - - expect(wrapper.find(ProductCard)).toHaveLength(1); - expect(wrapper.find(ProductCard).prop('product').ID).toEqual('workplaceSearch'); - }); + it('renders the Setup Guide and Product Selector', () => { + const wrapper = shallow(); - it('does not render the Workplace Search card if the user does not have access to WS', () => { - const wrapper = shallow( - - ); + expect(wrapper.find(SetupGuide)).toHaveLength(1); + expect(wrapper.find(ProductSelector)).toHaveLength(1); + }); - expect(wrapper.find(ProductCard)).toHaveLength(1); - expect(wrapper.find(ProductCard).prop('product').ID).toEqual('appSearch'); - }); + it('renders the error connecting prompt when host is not configured', () => { + (useValues as jest.Mock).mockReturnValueOnce({ errorConnecting: true }); + (useContext as jest.Mock).mockImplementationOnce(() => ({ config: { host: '' } })); - it('does not render any cards if the user does not have access', () => { - const wrapper = shallow(); + const wrapper = shallow(); - expect(wrapper.find(ProductCard)).toHaveLength(0); - }); + expect(wrapper.find(ErrorConnecting)).toHaveLength(1); + expect(wrapper.find(EuiPage)).toHaveLength(0); + expect(wrapper.find(ProductSelector)).toHaveLength(0); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx index 373f595a6a9ea..e2c05434dd0bb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx @@ -4,75 +4,37 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; -import { - EuiPage, - EuiPageBody, - EuiPageHeader, - EuiPageHeaderSection, - EuiPageContentBody, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiTitle, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; +import React, { useContext } from 'react'; +import { Route, Switch } from 'react-router-dom'; +import { useValues } from 'kea'; +import { KibanaContext, IKibanaContext } from '../index'; import { IInitialAppData } from '../../../common/types'; -import { APP_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN } from '../../../common/constants'; -import { SetEnterpriseSearchChrome as SetPageChrome } from '../shared/kibana_chrome'; -import { SendEnterpriseSearchTelemetry as SendTelemetry } from '../shared/telemetry'; +import { HttpLogic } from '../shared/http'; -import { ProductCard } from './components/product_card'; +import { ROOT_PATH, SETUP_GUIDE_PATH } from './routes'; + +import { ErrorConnecting } from './components/error_connecting'; +import { ProductSelector } from './components/product_selector'; +import { SetupGuide } from './components/setup_guide'; -import AppSearchImage from './assets/app_search.png'; -import WorkplaceSearchImage from './assets/workplace_search.png'; import './index.scss'; export const EnterpriseSearch: React.FC = ({ access = {} }) => { - const { hasAppSearchAccess, hasWorkplaceSearchAccess } = access; + const { errorConnecting } = useValues(HttpLogic); + const { config } = useContext(KibanaContext) as IKibanaContext; - return ( - - - + const showErrorConnecting = config.host && errorConnecting; - - - - -

- {i18n.translate('xpack.enterpriseSearch.overview.heading', { - defaultMessage: 'Welcome to Elastic Enterprise Search', - })} -

-
- -

- {i18n.translate('xpack.enterpriseSearch.overview.subheading', { - defaultMessage: 'Select a product to get started', - })} -

-
-
-
- - - {hasAppSearchAccess && ( - - - - )} - {hasWorkplaceSearchAccess && ( - - - - )} - - - -
-
+ return ( + + + + + + {showErrorConnecting ? : } + + ); }; diff --git a/x-pack/legacy/server/lib/create_router/call_with_request_factory/index.js b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/routes.ts similarity index 75% rename from x-pack/legacy/server/lib/create_router/call_with_request_factory/index.js rename to x-pack/plugins/enterprise_search/public/applications/enterprise_search/routes.ts index 787814d87dff9..1f9c06e9683ab 100644 --- a/x-pack/legacy/server/lib/create_router/call_with_request_factory/index.js +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/routes.ts @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { callWithRequestFactory } from './call_with_request_factory'; +export const ROOT_PATH = '/'; +export const SETUP_GUIDE_PATH = '/setup_guide'; diff --git a/x-pack/plugins/enterprise_search/public/applications/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/index.test.tsx index 053c450ab925e..6ee63ee22cae2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/index.test.tsx @@ -6,7 +6,6 @@ import React from 'react'; -import { AppMountParameters } from 'src/core/public'; import { coreMock } from 'src/core/public/mocks'; import { licensingMock } from '../../../licensing/public/mocks'; @@ -15,37 +14,38 @@ import { AppSearch } from './app_search'; import { WorkplaceSearch } from './workplace_search'; describe('renderApp', () => { - let params: AppMountParameters; - const core = coreMock.createStart(); - const plugins = { - licensing: licensingMock.createSetup(), + const kibanaDeps = { + params: coreMock.createAppMountParamters(), + core: coreMock.createStart(), + plugins: { licensing: licensingMock.createStart() }, + } as any; + const pluginData = { + config: {}, + data: {}, } as any; - const config = {}; - const data = {} as any; beforeEach(() => { jest.clearAllMocks(); - params = coreMock.createAppMountParamters(); }); it('mounts and unmounts UI', () => { const MockApp = () =>
Hello world!
; - const unmount = renderApp(MockApp, params, core, plugins, config, data); - expect(params.element.querySelector('.hello-world')).not.toBeNull(); + const unmount = renderApp(MockApp, kibanaDeps, pluginData); + expect(kibanaDeps.params.element.querySelector('.hello-world')).not.toBeNull(); unmount(); - expect(params.element.innerHTML).toEqual(''); + expect(kibanaDeps.params.element.innerHTML).toEqual(''); }); it('renders AppSearch', () => { - renderApp(AppSearch, params, core, plugins, config, data); - expect(params.element.querySelector('.setupGuide')).not.toBeNull(); + renderApp(AppSearch, kibanaDeps, pluginData); + expect(kibanaDeps.params.element.querySelector('.setupGuide')).not.toBeNull(); }); it('renders WorkplaceSearch', () => { - renderApp(WorkplaceSearch, params, core, plugins, config, data); - expect(params.element.querySelector('.setupGuide')).not.toBeNull(); + renderApp(WorkplaceSearch, kibanaDeps, pluginData); + expect(kibanaDeps.params.element.querySelector('.setupGuide')).not.toBeNull(); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/index.tsx b/x-pack/plugins/enterprise_search/public/applications/index.tsx index 43056f2f65538..4a25ecf6067cc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/index.tsx @@ -13,24 +13,17 @@ import { Store } from 'redux'; import { getContext, resetContext } from 'kea'; import { I18nProvider } from '@kbn/i18n/react'; -import { - AppMountParameters, - CoreStart, - ApplicationStart, - HttpSetup, - ChromeBreadcrumb, -} from 'src/core/public'; -import { ClientConfigType, ClientData, PluginsSetup } from '../plugin'; -import { LicenseProvider } from './shared/licensing'; -import { FlashMessagesProvider } from './shared/flash_messages'; -import { HttpProvider } from './shared/http'; +import { AppMountParameters, CoreStart, ApplicationStart, ChromeBreadcrumb } from 'src/core/public'; +import { PluginsStart, ClientConfigType, ClientData } from '../plugin'; +import { mountLicensingLogic } from './shared/licensing'; +import { mountHttpLogic } from './shared/http'; +import { mountFlashMessagesLogic } from './shared/flash_messages'; import { IExternalUrl } from './shared/enterprise_search_url'; import { IInitialAppData } from '../../common/types'; export interface IKibanaContext { config: { host?: string }; externalUrl: IExternalUrl; - http: HttpSetup; navigateToUrl: ApplicationStart['navigateToUrl']; setBreadcrumbs(crumbs: ChromeBreadcrumb[]): void; setDocTitle(title: string): void; @@ -46,46 +39,51 @@ export const KibanaContext = React.createContext({}); export const renderApp = ( App: React.FC, - params: AppMountParameters, - core: CoreStart, - plugins: PluginsSetup, - config: ClientConfigType, - { externalUrl, errorConnecting, ...initialData }: ClientData + { params, core, plugins }: { params: AppMountParameters; core: CoreStart; plugins: PluginsStart }, + { config, data }: { config: ClientConfigType; data: ClientData } ) => { + const { externalUrl, errorConnecting, ...initialData } = data; + resetContext({ createStore: true }); const store = getContext().store as Store; + const unmountLicensingLogic = mountLicensingLogic({ + license$: plugins.licensing.license$, + }); + + const unmountHttpLogic = mountHttpLogic({ + http: core.http, + errorConnecting, + readOnlyMode: initialData.readOnlyMode, + }); + + const unmountFlashMessagesLogic = mountFlashMessagesLogic({ history: params.history }); + ReactDOM.render( - - - - - - - - - + + + + + , params.element ); return () => { ReactDOM.unmountComponentAtNode(params.element); + unmountLicensingLogic(); + unmountHttpLogic(); + unmountFlashMessagesLogic(); }; }; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.test.ts index 136912847baa9..c12011b47a472 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.test.ts @@ -6,23 +6,25 @@ import { resetContext } from 'kea'; -import { FlashMessagesLogic, IFlashMessage } from './flash_messages_logic'; +import { mockHistory } from '../../__mocks__'; + +import { FlashMessagesLogic, mountFlashMessagesLogic, IFlashMessage } from './'; describe('FlashMessagesLogic', () => { - const DEFAULT_VALUES = { - messages: [], - queuedMessages: [], - historyListener: null, - }; + const mount = () => mountFlashMessagesLogic({ history: mockHistory as any }); beforeEach(() => { jest.clearAllMocks(); resetContext({}); }); - it('has expected default values', () => { - FlashMessagesLogic.mount(); - expect(FlashMessagesLogic.values).toEqual(DEFAULT_VALUES); + it('has default values', () => { + mount(); + expect(FlashMessagesLogic.values).toEqual({ + messages: [], + queuedMessages: [], + historyListener: expect.any(Function), + }); }); describe('setFlashMessages()', () => { @@ -33,7 +35,7 @@ describe('FlashMessagesLogic', () => { { type: 'info', message: 'Everything is fine, nothing is ruined' }, ]; - FlashMessagesLogic.mount(); + mount(); FlashMessagesLogic.actions.setFlashMessages(messages); expect(FlashMessagesLogic.values.messages).toEqual(messages); @@ -42,7 +44,7 @@ describe('FlashMessagesLogic', () => { it('automatically converts to an array if a single message obj is passed in', () => { const message = { type: 'success', message: 'I turn into an array!' } as IFlashMessage; - FlashMessagesLogic.mount(); + mount(); FlashMessagesLogic.actions.setFlashMessages(message); expect(FlashMessagesLogic.values.messages).toEqual([message]); @@ -51,7 +53,7 @@ describe('FlashMessagesLogic', () => { describe('clearFlashMessages()', () => { it('sets messages back to an empty array', () => { - FlashMessagesLogic.mount(); + mount(); FlashMessagesLogic.actions.setFlashMessages('test' as any); FlashMessagesLogic.actions.clearFlashMessages(); @@ -63,7 +65,7 @@ describe('FlashMessagesLogic', () => { it('sets an array of messages', () => { const queuedMessage: IFlashMessage = { type: 'error', message: 'You deleted a thing' }; - FlashMessagesLogic.mount(); + mount(); FlashMessagesLogic.actions.setQueuedMessages(queuedMessage); expect(FlashMessagesLogic.values.queuedMessages).toEqual([queuedMessage]); @@ -72,7 +74,7 @@ describe('FlashMessagesLogic', () => { describe('clearQueuedMessages()', () => { it('sets queued messages back to an empty array', () => { - FlashMessagesLogic.mount(); + mount(); FlashMessagesLogic.actions.setQueuedMessages('test' as any); FlashMessagesLogic.actions.clearQueuedMessages(); @@ -83,30 +85,25 @@ describe('FlashMessagesLogic', () => { describe('history listener logic', () => { describe('setHistoryListener()', () => { it('sets the historyListener value', () => { - FlashMessagesLogic.mount(); + mount(); FlashMessagesLogic.actions.setHistoryListener('test' as any); expect(FlashMessagesLogic.values.historyListener).toEqual('test'); }); }); - describe('listenToHistory()', () => { + describe('on mount', () => { it('listens for history changes and clears messages on change', () => { - FlashMessagesLogic.mount(); + mount(); + expect(mockHistory.listen).toHaveBeenCalled(); + FlashMessagesLogic.actions.setQueuedMessages(['queuedMessages'] as any); jest.spyOn(FlashMessagesLogic.actions, 'clearFlashMessages'); jest.spyOn(FlashMessagesLogic.actions, 'setFlashMessages'); jest.spyOn(FlashMessagesLogic.actions, 'clearQueuedMessages'); jest.spyOn(FlashMessagesLogic.actions, 'setHistoryListener'); - const mockListener = jest.fn(() => jest.fn()); - const history = { listen: mockListener } as any; - FlashMessagesLogic.actions.listenToHistory(history); - - expect(mockListener).toHaveBeenCalled(); - expect(FlashMessagesLogic.actions.setHistoryListener).toHaveBeenCalled(); - - const mockHistoryChange = (mockListener.mock.calls[0] as any)[0]; + const mockHistoryChange = (mockHistory.listen.mock.calls[0] as any)[0]; mockHistoryChange(); expect(FlashMessagesLogic.actions.clearFlashMessages).toHaveBeenCalled(); expect(FlashMessagesLogic.actions.setFlashMessages).toHaveBeenCalledWith([ @@ -116,19 +113,20 @@ describe('FlashMessagesLogic', () => { }); }); - describe('beforeUnmount', () => { - it('removes history listener on unmount', () => { + describe('on unmount', () => { + it('removes history listener', () => { const mockUnlistener = jest.fn(); - const unmount = FlashMessagesLogic.mount(); + mockHistory.listen.mockReturnValueOnce(mockUnlistener); - FlashMessagesLogic.actions.setHistoryListener(mockUnlistener); + const unmount = mount(); unmount(); expect(mockUnlistener).toHaveBeenCalled(); }); it('does not crash if no listener exists', () => { - const unmount = FlashMessagesLogic.mount(); + const unmount = mount(); + FlashMessagesLogic.actions.setHistoryListener(null as any); unmount(); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.ts index 37a8f16acad6d..1735cc8ac7228 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_logic.ts @@ -24,7 +24,6 @@ export interface IFlashMessagesActions { clearFlashMessages(): void; setQueuedMessages(messages: IFlashMessage | IFlashMessage[]): { messages: IFlashMessage[] }; clearQueuedMessages(): void; - listenToHistory(history: History): History; setHistoryListener(historyListener: Function): { historyListener: Function }; } @@ -38,7 +37,6 @@ export const FlashMessagesLogic = kea null, setQueuedMessages: (messages) => ({ messages: convertToArray(messages) }), clearQueuedMessages: () => null, - listenToHistory: (history) => history, setHistoryListener: (historyListener) => ({ historyListener }), }, reducers: { @@ -63,21 +61,31 @@ export const FlashMessagesLogic = kea ({ - listenToHistory: (history) => { + events: ({ props, values, actions }) => ({ + afterMount: () => { // On React Router navigation, clear previous flash messages and load any queued messages - const unlisten = history.listen(() => { + const unlisten = props.history.listen(() => { actions.clearFlashMessages(); actions.setFlashMessages(values.queuedMessages); actions.clearQueuedMessages(); }); actions.setHistoryListener(unlisten); }, - }), - events: ({ values }) => ({ beforeUnmount: () => { const { historyListener: removeHistoryListener } = values; if (removeHistoryListener) removeHistoryListener(); }, }), }); + +/** + * Mount/props helper + */ +interface IFlashMessagesLogicProps { + history: History; +} +export const mountFlashMessagesLogic = (props: IFlashMessagesLogicProps) => { + FlashMessagesLogic(props); + const unmount = FlashMessagesLogic.mount(); + return unmount; +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_provider.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_provider.test.tsx deleted file mode 100644 index bcd7abd6d7ce2..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_provider.test.tsx +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import '../../__mocks__/shallow_usecontext.mock'; -import '../../__mocks__/kea.mock'; - -import React from 'react'; -import { shallow } from 'enzyme'; -import { useValues, useActions } from 'kea'; - -import { mockHistory } from '../../__mocks__'; - -import { FlashMessagesProvider } from './'; - -describe('FlashMessagesProvider', () => { - const props = { history: mockHistory as any }; - const listenToHistory = jest.fn(); - - beforeEach(() => { - jest.clearAllMocks(); - (useActions as jest.Mock).mockImplementationOnce(() => ({ listenToHistory })); - }); - - it('does not render', () => { - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); - - it('listens to history on mount', () => { - shallow(); - - expect(listenToHistory).toHaveBeenCalledWith(mockHistory); - }); - - it('does not add another history listener if one already exists', () => { - (useValues as jest.Mock).mockImplementationOnce(() => ({ historyListener: 'exists' as any })); - - shallow(); - - expect(listenToHistory).not.toHaveBeenCalledWith(props); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_provider.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_provider.tsx deleted file mode 100644 index a3ceabcf6ac8a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages_provider.tsx +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { useEffect } from 'react'; -import { useValues, useActions } from 'kea'; -import { History } from 'history'; - -import { FlashMessagesLogic } from './flash_messages_logic'; - -interface IFlashMessagesProviderProps { - history: History; -} - -export const FlashMessagesProvider: React.FC = ({ history }) => { - const { historyListener } = useValues(FlashMessagesLogic); - const { listenToHistory } = useActions(FlashMessagesLogic); - - useEffect(() => { - if (!historyListener) listenToHistory(history); - }, []); - - return null; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/index.ts index c4daeb44420c8..21c1a60efa6b7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/index.ts @@ -10,7 +10,7 @@ export { IFlashMessage, IFlashMessagesValues, IFlashMessagesActions, + mountFlashMessagesLogic, } from './flash_messages_logic'; -export { FlashMessagesProvider } from './flash_messages_provider'; export { flashAPIErrors } from './handle_api_errors'; export { setSuccessMessage, setErrorMessage, setQueuedSuccessMessage } from './set_message_helpers'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/set_message_helpers.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/set_message_helpers.test.ts index c3c60d77f4577..f2ddd560ac9c1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/set_message_helpers.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/set_message_helpers.test.ts @@ -4,8 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ +import { mockHistory } from '../../__mocks__'; + import { FlashMessagesLogic, + mountFlashMessagesLogic, setSuccessMessage, setErrorMessage, setQueuedSuccessMessage, @@ -15,7 +18,7 @@ describe('Flash Message Helpers', () => { const message = 'I am a message'; beforeEach(() => { - FlashMessagesLogic.mount(); + mountFlashMessagesLogic({ history: mockHistory as any }); }); it('setSuccessMessage()', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.test.ts index b65499be2f7c0..df32b5496c367 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.test.ts @@ -8,31 +8,20 @@ import { resetContext } from 'kea'; import { httpServiceMock } from 'src/core/public/mocks'; -import { HttpLogic } from './http_logic'; +import { HttpLogic, mountHttpLogic } from './http_logic'; describe('HttpLogic', () => { const mockHttp = httpServiceMock.createSetupContract(); - const DEFAULT_VALUES = { - http: null, - httpInterceptors: [], - errorConnecting: false, - readOnlyMode: false, - }; + const mount = () => mountHttpLogic({ http: mockHttp }); beforeEach(() => { jest.clearAllMocks(); resetContext({}); }); - it('has expected default values', () => { - HttpLogic.mount(); - expect(HttpLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('initializeHttp()', () => { - it('sets values based on passed props', () => { - HttpLogic.mount(); - HttpLogic.actions.initializeHttp({ + describe('mounts', () => { + it('sets values from props', () => { + mountHttpLogic({ http: mockHttp, errorConnecting: true, readOnlyMode: true, @@ -40,7 +29,7 @@ describe('HttpLogic', () => { expect(HttpLogic.values).toEqual({ http: mockHttp, - httpInterceptors: [], + httpInterceptors: expect.any(Array), errorConnecting: true, readOnlyMode: true, }); @@ -49,7 +38,9 @@ describe('HttpLogic', () => { describe('setErrorConnecting()', () => { it('sets errorConnecting value', () => { - HttpLogic.mount(); + mount(); + expect(HttpLogic.values.errorConnecting).toEqual(false); + HttpLogic.actions.setErrorConnecting(true); expect(HttpLogic.values.errorConnecting).toEqual(true); @@ -60,7 +51,9 @@ describe('HttpLogic', () => { describe('setReadOnlyMode()', () => { it('sets readOnlyMode value', () => { - HttpLogic.mount(); + mount(); + expect(HttpLogic.values.readOnlyMode).toEqual(false); + HttpLogic.actions.setReadOnlyMode(true); expect(HttpLogic.values.readOnlyMode).toEqual(true); @@ -72,10 +65,8 @@ describe('HttpLogic', () => { describe('http interceptors', () => { describe('initializeHttpInterceptors()', () => { beforeEach(() => { - HttpLogic.mount(); + mount(); jest.spyOn(HttpLogic.actions, 'setHttpInterceptors'); - HttpLogic.actions.initializeHttp({ http: mockHttp }); - HttpLogic.actions.initializeHttpInterceptors(); }); it('calls http.intercept and sets an array of interceptors', () => { @@ -165,7 +156,7 @@ describe('HttpLogic', () => { }); it('sets httpInterceptors and calls all valid remove functions on unmount', () => { - const unmount = HttpLogic.mount(); + const unmount = mount(); const httpInterceptors = [jest.fn(), undefined, jest.fn()] as any; HttpLogic.actions.setHttpInterceptors(httpInterceptors); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts index 72380142fe399..d16e507bfb3bc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts @@ -7,7 +7,6 @@ import { kea, MakeLogicType } from 'kea'; import { HttpSetup, HttpInterceptorResponseError, HttpResponse } from 'src/core/public'; -import { IHttpProviderProps } from './http_provider'; import { READ_ONLY_MODE_HEADER } from '../../../../common/constants'; @@ -18,7 +17,6 @@ export interface IHttpValues { readOnlyMode: boolean; } export interface IHttpActions { - initializeHttp({ http, errorConnecting, readOnlyMode }: IHttpProviderProps): IHttpProviderProps; initializeHttpInterceptors(): void; setHttpInterceptors(httpInterceptors: Function[]): { httpInterceptors: Function[] }; setErrorConnecting(errorConnecting: boolean): { errorConnecting: boolean }; @@ -28,19 +26,13 @@ export interface IHttpActions { export const HttpLogic = kea>({ path: ['enterprise_search', 'http_logic'], actions: { - initializeHttp: (props) => props, initializeHttpInterceptors: () => null, setHttpInterceptors: (httpInterceptors) => ({ httpInterceptors }), setErrorConnecting: (errorConnecting) => ({ errorConnecting }), setReadOnlyMode: (readOnlyMode) => ({ readOnlyMode }), }, - reducers: { - http: [ - (null as unknown) as HttpSetup, - { - initializeHttp: (_, { http }) => http, - }, - ], + reducers: ({ props }) => ({ + http: [props.http, {}], httpInterceptors: [ [], { @@ -48,20 +40,18 @@ export const HttpLogic = kea>({ }, ], errorConnecting: [ - false, + props.errorConnecting || false, { - initializeHttp: (_, { errorConnecting }) => !!errorConnecting, setErrorConnecting: (_, { errorConnecting }) => errorConnecting, }, ], readOnlyMode: [ - false, + props.readOnlyMode || false, { - initializeHttp: (_, { readOnlyMode }) => !!readOnlyMode, setReadOnlyMode: (_, { readOnlyMode }) => readOnlyMode, }, ], - }, + }), listeners: ({ values, actions }) => ({ initializeHttpInterceptors: () => { const httpInterceptors = []; @@ -103,7 +93,10 @@ export const HttpLogic = kea>({ actions.setHttpInterceptors(httpInterceptors); }, }), - events: ({ values }) => ({ + events: ({ values, actions }) => ({ + afterMount: () => { + actions.initializeHttpInterceptors(); + }, beforeUnmount: () => { values.httpInterceptors.forEach((removeInterceptorFn?: Function) => { if (removeInterceptorFn) removeInterceptorFn(); @@ -112,6 +105,20 @@ export const HttpLogic = kea>({ }), }); +/** + * Mount/props helper + */ +interface IHttpLogicProps { + http: HttpSetup; + errorConnecting?: boolean; + readOnlyMode?: boolean; +} +export const mountHttpLogic = (props: IHttpLogicProps) => { + HttpLogic(props); + const unmount = HttpLogic.mount(); + return unmount; +}; + /** * Small helper that checks whether or not an http call is for an Enterprise Search API */ diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_provider.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_provider.test.tsx deleted file mode 100644 index 902c910f10d7c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_provider.test.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import '../../__mocks__/shallow_usecontext.mock'; -import '../../__mocks__/kea.mock'; - -import React from 'react'; -import { shallow } from 'enzyme'; -import { useActions } from 'kea'; - -import { HttpProvider } from './'; - -describe('HttpProvider', () => { - const props = { - http: {} as any, - errorConnecting: false, - readOnlyMode: false, - }; - const initializeHttp = jest.fn(); - const initializeHttpInterceptors = jest.fn(); - - beforeEach(() => { - jest.clearAllMocks(); - (useActions as jest.Mock).mockImplementationOnce(() => ({ - initializeHttp, - initializeHttpInterceptors, - })); - }); - - it('does not render', () => { - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); - - it('calls initialization actions on mount', () => { - shallow(); - - expect(initializeHttp).toHaveBeenCalledWith(props); - expect(initializeHttpInterceptors).toHaveBeenCalled(); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_provider.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_provider.tsx deleted file mode 100644 index db1b0d611079a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_provider.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { useEffect } from 'react'; -import { useActions } from 'kea'; - -import { HttpSetup } from 'src/core/public'; - -import { HttpLogic } from './http_logic'; - -export interface IHttpProviderProps { - http: HttpSetup; - errorConnecting?: boolean; - readOnlyMode?: boolean; -} - -export const HttpProvider: React.FC = (props) => { - const { initializeHttp, initializeHttpInterceptors } = useActions(HttpLogic); - - useEffect(() => { - initializeHttp(props); - initializeHttpInterceptors(); - }, []); - - return null; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/http/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/http/index.ts index db65e80ca25c2..46a52415f8564 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/http/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/http/index.ts @@ -4,5 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { HttpLogic, IHttpValues, IHttpActions } from './http_logic'; -export { HttpProvider } from './http_provider'; +export { HttpLogic, IHttpValues, IHttpActions, mountHttpLogic } from './http_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/index.ts index 29c11ffa1cef8..4e371b337c40a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/index.ts @@ -4,5 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { LicenseContext, LicenseProvider, ILicenseContext } from './license_context'; -export { hasPlatinumLicense, hasGoldLicense } from './license_checks'; +export { LicensingLogic, mountLicensingLogic } from './licensing_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.test.ts deleted file mode 100644 index 40f0f6380c21c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { hasPlatinumLicense, hasGoldLicense } from './license_checks'; - -describe('hasPlatinumLicense', () => { - it('is true for platinum licenses', () => { - expect(hasPlatinumLicense({ isActive: true, type: 'platinum' } as any)).toEqual(true); - }); - - it('is true for enterprise licenses', () => { - expect(hasPlatinumLicense({ isActive: true, type: 'enterprise' } as any)).toEqual(true); - }); - - it('is true for trial licenses', () => { - expect(hasPlatinumLicense({ isActive: true, type: 'platinum' } as any)).toEqual(true); - }); - - it('is false if the current license is expired', () => { - expect(hasPlatinumLicense({ isActive: false, type: 'platinum' } as any)).toEqual(false); - expect(hasPlatinumLicense({ isActive: false, type: 'enterprise' } as any)).toEqual(false); - expect(hasPlatinumLicense({ isActive: false, type: 'trial' } as any)).toEqual(false); - }); - - it('is false for licenses below platinum', () => { - expect(hasPlatinumLicense({ isActive: true, type: 'basic' } as any)).toEqual(false); - expect(hasPlatinumLicense({ isActive: false, type: 'standard' } as any)).toEqual(false); - expect(hasPlatinumLicense({ isActive: true, type: 'gold' } as any)).toEqual(false); - }); -}); - -describe('hasGoldLicense', () => { - it('is true for gold+ and trial licenses', () => { - expect(hasGoldLicense({ isActive: true, type: 'gold' } as any)).toEqual(true); - expect(hasGoldLicense({ isActive: true, type: 'platinum' } as any)).toEqual(true); - expect(hasGoldLicense({ isActive: true, type: 'enterprise' } as any)).toEqual(true); - expect(hasGoldLicense({ isActive: true, type: 'trial' } as any)).toEqual(true); - }); - - it('is false if the current license is expired', () => { - expect(hasGoldLicense({ isActive: false, type: 'gold' } as any)).toEqual(false); - expect(hasGoldLicense({ isActive: false, type: 'platinum' } as any)).toEqual(false); - expect(hasGoldLicense({ isActive: false, type: 'enterprise' } as any)).toEqual(false); - expect(hasGoldLicense({ isActive: false, type: 'trial' } as any)).toEqual(false); - }); - - it('is false for licenses below gold', () => { - expect(hasGoldLicense({ isActive: true, type: 'basic' } as any)).toEqual(false); - expect(hasGoldLicense({ isActive: false, type: 'standard' } as any)).toEqual(false); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.ts b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.ts deleted file mode 100644 index d13d0909243be..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { ILicense } from '../../../../../licensing/public'; - -export const hasPlatinumLicense = (license?: ILicense) => { - const qualifyingLicenses = ['platinum', 'enterprise', 'trial']; - return license?.isActive && qualifyingLicenses.includes(license?.type as string); -}; - -export const hasGoldLicense = (license?: ILicense) => { - const qualifyingLicenses = ['gold', 'platinum', 'enterprise', 'trial']; - return license?.isActive && qualifyingLicenses.includes(license?.type as string); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.test.tsx deleted file mode 100644 index c65474ec1f590..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.test.tsx +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { useContext } from 'react'; - -import { mountWithContext } from '../../__mocks__'; -import { LicenseContext, ILicenseContext } from './'; - -describe('LicenseProvider', () => { - const MockComponent: React.FC = () => { - const { license } = useContext(LicenseContext) as ILicenseContext; - return
{license?.type}
; - }; - - it('renders children', () => { - const wrapper = mountWithContext(, { license: { type: 'basic' } }); - - expect(wrapper.find('.license-test')).toHaveLength(1); - expect(wrapper.text()).toEqual('basic'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.tsx deleted file mode 100644 index 9b47959ff7544..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_context.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import useObservable from 'react-use/lib/useObservable'; -import { Observable } from 'rxjs'; - -import { ILicense } from '../../../../../licensing/public'; - -export interface ILicenseContext { - license: ILicense; -} -interface ILicenseContextProps { - license$: Observable; - children: React.ReactNode; -} - -export const LicenseContext = React.createContext({}); - -export const LicenseProvider: React.FC = ({ license$, children }) => { - // Listen for changes to license subscription - const license = useObservable(license$); - - // Render rest of application and pass down license via context - return ; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/licensing_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/licensing_logic.test.ts new file mode 100644 index 0000000000000..153a5ae765468 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/licensing_logic.test.ts @@ -0,0 +1,161 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { resetContext } from 'kea'; +import { BehaviorSubject } from 'rxjs'; + +import { licensingMock } from '../../../../../licensing/public/mocks'; + +import { LicensingLogic, mountLicensingLogic } from './licensing_logic'; + +describe('LicensingLogic', () => { + const mockLicense = licensingMock.createLicense(); + const mockLicense$ = new BehaviorSubject(mockLicense); + const mount = () => mountLicensingLogic({ license$: mockLicense$ }); + + beforeEach(() => { + jest.clearAllMocks(); + resetContext({}); + }); + + describe('setLicense()', () => { + it('sets license value', () => { + mount(); + LicensingLogic.actions.setLicense('test' as any); + expect(LicensingLogic.values.license).toEqual('test'); + }); + }); + + describe('setLicenseSubscription()', () => { + it('sets licenseSubscription value', () => { + mount(); + LicensingLogic.actions.setLicenseSubscription('test' as any); + expect(LicensingLogic.values.licenseSubscription).toEqual('test'); + }); + }); + + describe('licensing subscription', () => { + describe('on mount', () => { + it('subscribes to the license observable', () => { + mount(); + expect(LicensingLogic.values.license).toEqual(mockLicense); + expect(LicensingLogic.values.licenseSubscription).not.toBeNull(); + }); + }); + + describe('on subscription update', () => { + it('updates the license value', () => { + mount(); + + const nextMockLicense = licensingMock.createLicense({ license: { status: 'invalid' } }); + mockLicense$.next(nextMockLicense); + + expect(LicensingLogic.values.license).toEqual(nextMockLicense); + }); + }); + + describe('on unmount', () => { + it('unsubscribes to the license observable', () => { + const mockUnsubscribe = jest.fn(); + const unmount = mountLicensingLogic({ + license$: { subscribe: () => ({ unsubscribe: mockUnsubscribe }) } as any, + }); + unmount(); + expect(mockUnsubscribe).toHaveBeenCalled(); + }); + + it('does not crash if no subscription exists', () => { + const unmount = mount(); + LicensingLogic.actions.setLicenseSubscription(null as any); + unmount(); + }); + }); + }); + + describe('license check selectors', () => { + beforeEach(() => { + mount(); + }); + + const updateLicense = (license: any) => { + const updatedLicense = licensingMock.createLicense({ license }); + mockLicense$.next(updatedLicense); + }; + + describe('hasPlatinumLicense', () => { + it('is true for platinum+ and trial licenses', () => { + updateLicense({ status: 'active', type: 'platinum' }); + expect(LicensingLogic.values.hasPlatinumLicense).toEqual(true); + + updateLicense({ status: 'active', type: 'enterprise' }); + expect(LicensingLogic.values.hasPlatinumLicense).toEqual(true); + + updateLicense({ status: 'active', type: 'trial' }); + expect(LicensingLogic.values.hasPlatinumLicense).toEqual(true); + }); + + it('is false if the current license is expired', () => { + updateLicense({ status: 'expired', type: 'platinum' }); + expect(LicensingLogic.values.hasPlatinumLicense).toEqual(false); + + updateLicense({ status: 'expired', type: 'enterprise' }); + expect(LicensingLogic.values.hasPlatinumLicense).toEqual(false); + + updateLicense({ status: 'expired', type: 'trial' }); + expect(LicensingLogic.values.hasPlatinumLicense).toEqual(false); + }); + + it('is false for licenses below platinum', () => { + updateLicense({ status: 'active', type: 'basic' }); + expect(LicensingLogic.values.hasPlatinumLicense).toEqual(false); + + updateLicense({ status: 'active', type: 'standard' }); + expect(LicensingLogic.values.hasPlatinumLicense).toEqual(false); + + updateLicense({ status: 'active', type: 'gold' }); + expect(LicensingLogic.values.hasPlatinumLicense).toEqual(false); + }); + }); + + describe('hasGoldLicense', () => { + it('is true for gold+ and trial licenses', () => { + updateLicense({ status: 'active', type: 'gold' }); + expect(LicensingLogic.values.hasGoldLicense).toEqual(true); + + updateLicense({ status: 'active', type: 'platinum' }); + expect(LicensingLogic.values.hasGoldLicense).toEqual(true); + + updateLicense({ status: 'active', type: 'enterprise' }); + expect(LicensingLogic.values.hasGoldLicense).toEqual(true); + + updateLicense({ status: 'active', type: 'trial' }); + expect(LicensingLogic.values.hasGoldLicense).toEqual(true); + }); + + it('is false if the current license is expired', () => { + updateLicense({ status: 'expired', type: 'gold' }); + expect(LicensingLogic.values.hasGoldLicense).toEqual(false); + + updateLicense({ status: 'expired', type: 'platinum' }); + expect(LicensingLogic.values.hasGoldLicense).toEqual(false); + + updateLicense({ status: 'expired', type: 'enterprise' }); + expect(LicensingLogic.values.hasGoldLicense).toEqual(false); + + updateLicense({ status: 'expired', type: 'trial' }); + expect(LicensingLogic.values.hasGoldLicense).toEqual(false); + }); + + it('is false for licenses below gold', () => { + updateLicense({ status: 'active', type: 'basic' }); + expect(LicensingLogic.values.hasGoldLicense).toEqual(false); + + updateLicense({ status: 'active', type: 'standard' }); + expect(LicensingLogic.values.hasGoldLicense).toEqual(false); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/licensing_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/licensing_logic.ts new file mode 100644 index 0000000000000..ae31b2ec6168a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/licensing_logic.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { kea, MakeLogicType } from 'kea'; +import { Observable, Subscription } from 'rxjs'; + +import { ILicense } from '../../../../../licensing/public'; + +export interface ILicensingValues { + license: ILicense | null; + licenseSubscription: Subscription | null; + hasPlatinumLicense: boolean; + hasGoldLicense: boolean; +} +export interface ILicensingActions { + setLicense(license: ILicense): ILicense; + setLicenseSubscription(licenseSubscription: Subscription): Subscription; +} + +export const LicensingLogic = kea>({ + path: ['enterprise_search', 'licensing_logic'], + actions: { + setLicense: (license) => license, + setLicenseSubscription: (licenseSubscription) => licenseSubscription, + }, + reducers: { + license: [ + null, + { + setLicense: (_, license) => license, + }, + ], + licenseSubscription: [ + null, + { + setLicenseSubscription: (_, licenseSubscription) => licenseSubscription, + }, + ], + }, + selectors: { + hasPlatinumLicense: [ + (selectors) => [selectors.license], + (license) => { + const qualifyingLicenses = ['platinum', 'enterprise', 'trial']; + return license?.isActive && qualifyingLicenses.includes(license?.type); + }, + ], + hasGoldLicense: [ + (selectors) => [selectors.license], + (license) => { + const qualifyingLicenses = ['gold', 'platinum', 'enterprise', 'trial']; + return license?.isActive && qualifyingLicenses.includes(license?.type); + }, + ], + }, + events: ({ props, actions, values }) => ({ + afterMount: () => { + const licenseSubscription = props.license$.subscribe(async (license: ILicense) => { + actions.setLicense(license); + }); + actions.setLicenseSubscription(licenseSubscription); + }, + beforeUnmount: () => { + if (values.licenseSubscription) values.licenseSubscription.unsubscribe(); + }, + }), +}); + +/** + * Mount/props helper + */ +interface ILicensingLogicProps { + license$: Observable; +} +export const mountLicensingLogic = (props: ILicensingLogicProps) => { + LicensingLogic(props); + const unmount = LicensingLogic.mount(); + return unmount; +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.test.tsx index ce9071ad7b9d0..62c0af31cffd9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.test.tsx @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import '../../__mocks__/shallow_usecontext.mock'; +import '../../__mocks__/kea.mock'; -import React, { useContext } from 'react'; +import React from 'react'; +import { useValues } from 'kea'; import { shallow } from 'enzyme'; import { EuiButton as EuiButtonExternal, EuiEmptyPrompt } from '@elastic/eui'; @@ -18,13 +19,6 @@ import { WorkplaceSearchLogo } from './assets/workplace_search_logo'; import { NotFound } from './'; describe('NotFound', () => { - const basicLicense = { isActive: true, type: 'basic' }; - const goldLicense = { isActive: true, type: 'gold' }; - - beforeEach(() => { - (useContext as jest.Mock).mockImplementation(() => ({ license: basicLicense })); - }); - it('renders an App Search 404 view', () => { const wrapper = shallow(); const prompt = wrapper.find(EuiEmptyPrompt).dive().shallow(); @@ -50,7 +44,7 @@ describe('NotFound', () => { }); it('changes the support URL if the user has a gold+ license', () => { - (useContext as jest.Mock).mockImplementation(() => ({ license: goldLicense })); + (useValues as jest.Mock).mockReturnValueOnce({ hasGoldLicense: true }); const wrapper = shallow(); const prompt = wrapper.find(EuiEmptyPrompt).dive().shallow(); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx index bd988854225fb..40bb5efcc6330 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext } from 'react'; +import React from 'react'; +import { useValues } from 'kea'; import { i18n } from '@kbn/i18n'; import { EuiPageContent, @@ -24,7 +25,7 @@ import { import { EuiButton } from '../react_router_helpers'; import { SetAppSearchChrome, SetWorkplaceSearchChrome } from '../kibana_chrome'; import { SendAppSearchTelemetry, SendWorkplaceSearchTelemetry } from '../telemetry'; -import { LicenseContext, ILicenseContext, hasGoldLicense } from '../licensing'; +import { LicensingLogic } from '../licensing'; import { AppSearchLogo } from './assets/app_search_logo'; import { WorkplaceSearchLogo } from './assets/workplace_search_logo'; @@ -39,8 +40,8 @@ interface NotFoundProps { } export const NotFound: React.FC = ({ product = {} }) => { - const { license } = useContext(LicenseContext) as ILicenseContext; - const supportUrl = hasGoldLicense(license) ? LICENSED_SUPPORT_URL : product.SUPPORT_URL; + const { hasGoldLicense } = useValues(LicensingLogic); + const supportUrl = hasGoldLicense ? LICENSED_SUPPORT_URL : product.SUPPORT_URL; let Logo; let SetPageChrome; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.test.tsx index 8f7cf090e2d57..073c548ba47fa 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.test.tsx @@ -4,11 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ +import '../../__mocks__/kea.mock'; +import '../../__mocks__/shallow_usecontext.mock'; +import { mockHttpValues } from '../../__mocks__'; + import React from 'react'; +import { shallow } from 'enzyme'; -import { httpServiceMock } from 'src/core/public/mocks'; import { JSON_HEADER as headers } from '../../../../common/constants'; -import { mountWithKibanaContext } from '../../__mocks__'; import { sendTelemetry, @@ -18,8 +21,6 @@ import { } from './'; describe('Shared Telemetry Helpers', () => { - const httpMock = httpServiceMock.createSetupContract(); - beforeEach(() => { jest.clearAllMocks(); }); @@ -27,13 +28,13 @@ describe('Shared Telemetry Helpers', () => { describe('sendTelemetry', () => { it('successfully calls the server-side telemetry endpoint', () => { sendTelemetry({ - http: httpMock, + http: mockHttpValues.http, product: 'enterprise_search', action: 'viewed', metric: 'setup_guide', }); - expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/telemetry', { + expect(mockHttpValues.http.put).toHaveBeenCalledWith('/api/enterprise_search/stats', { headers, body: '{"product":"enterprise_search","action":"viewed","metric":"setup_guide"}', }); @@ -50,33 +51,27 @@ describe('Shared Telemetry Helpers', () => { describe('React component helpers', () => { it('SendEnterpriseSearchTelemetry component', () => { - mountWithKibanaContext(, { - http: httpMock, - }); + shallow(); - expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/telemetry', { + expect(mockHttpValues.http.put).toHaveBeenCalledWith('/api/enterprise_search/stats', { headers, body: '{"product":"enterprise_search","action":"viewed","metric":"page"}', }); }); it('SendAppSearchTelemetry component', () => { - mountWithKibanaContext(, { - http: httpMock, - }); + shallow(); - expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/telemetry', { + expect(mockHttpValues.http.put).toHaveBeenCalledWith('/api/enterprise_search/stats', { headers, body: '{"product":"app_search","action":"clicked","metric":"button"}', }); }); it('SendWorkplaceSearchTelemetry component', () => { - mountWithKibanaContext(, { - http: httpMock, - }); + shallow(); - expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/telemetry', { + expect(mockHttpValues.http.put).toHaveBeenCalledWith('/api/enterprise_search/stats', { headers, body: '{"product":"workplace_search","action":"error","metric":"not_found"}', }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.tsx index 4df1428221de6..2f87597897b41 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.tsx @@ -4,11 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext, useEffect } from 'react'; +import React, { useEffect } from 'react'; +import { useValues } from 'kea'; import { HttpSetup } from 'src/core/public'; import { JSON_HEADER as headers } from '../../../../common/constants'; -import { KibanaContext, IKibanaContext } from '../../index'; +import { HttpLogic } from '../http'; interface ISendTelemetryProps { action: 'viewed' | 'error' | 'clicked'; @@ -27,7 +28,7 @@ interface ISendTelemetry extends ISendTelemetryProps { export const sendTelemetry = async ({ http, product, action, metric }: ISendTelemetry) => { try { const body = JSON.stringify({ product, action, metric }); - await http.put('/api/enterprise_search/telemetry', { headers, body }); + await http.put('/api/enterprise_search/stats', { headers, body }); } catch (error) { throw new Error('Unable to send telemetry'); } @@ -41,7 +42,7 @@ export const SendEnterpriseSearchTelemetry: React.FC = ({ action, metric, }) => { - const { http } = useContext(KibanaContext) as IKibanaContext; + const { http } = useValues(HttpLogic); useEffect(() => { sendTelemetry({ http, action, metric, product: 'enterprise_search' }); @@ -51,7 +52,7 @@ export const SendEnterpriseSearchTelemetry: React.FC = ({ }; export const SendAppSearchTelemetry: React.FC = ({ action, metric }) => { - const { http } = useContext(KibanaContext) as IKibanaContext; + const { http } = useValues(HttpLogic); useEffect(() => { sendTelemetry({ http, action, metric, product: 'app_search' }); @@ -61,7 +62,7 @@ export const SendAppSearchTelemetry: React.FC = ({ action, }; export const SendWorkplaceSearchTelemetry: React.FC = ({ action, metric }) => { - const { http } = useContext(KibanaContext) as IKibanaContext; + const { http } = useValues(HttpLogic); useEffect(() => { sendTelemetry({ http, action, metric, product: 'workplace_search' }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts index c52eceb2d2fdd..974e07069ddba 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts @@ -50,5 +50,15 @@ describe('AppLogic', () => { expect(AppLogic.values).toEqual(expectedLogicValues); }); + + it('gracefully handles missing initial data', () => { + AppLogic.actions.initializeAppData({}); + + expect(AppLogic.values).toEqual({ + ...DEFAULT_VALUES, + hasInitialized: true, + isFederatedAuth: false, + }); + }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts index 94bd1d529b65f..629d1969a8f59 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts @@ -21,6 +21,9 @@ export interface IAppActions { initializeAppData(props: IInitialAppData): IInitialAppData; } +const emptyOrg = {} as IOrganization; +const emptyAccount = {} as IAccount; + export const AppLogic = kea>({ path: ['enterprise_search', 'workplace_search', 'app_logic'], actions: { @@ -43,15 +46,15 @@ export const AppLogic = kea>({ }, ], organization: [ - {} as IOrganization, + emptyOrg, { - initializeAppData: (_, { workplaceSearch }) => workplaceSearch!.organization, + initializeAppData: (_, { workplaceSearch }) => workplaceSearch?.organization || emptyOrg, }, ], account: [ - {} as IAccount, + emptyAccount, { - initializeAppData: (_, { workplaceSearch }) => workplaceSearch!.account, + initializeAppData: (_, { workplaceSearch }) => workplaceSearch?.account || emptyAccount, }, ], }, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/product_button/product_button.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/product_button/product_button.test.tsx index 429a2c509813d..c73eb05ccec16 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/product_button/product_button.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/product_button/product_button.test.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import '../../../../__mocks__/kea.mock'; import '../../../../__mocks__/shallow_usecontext.mock'; import React from 'react'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/product_button/product_button.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/product_button/product_button.tsx index a914000654165..a80de9fd6ac82 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/product_button/product_button.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/product_button/product_button.tsx @@ -5,17 +5,19 @@ */ import React, { useContext } from 'react'; +import { useValues } from 'kea'; import { EuiButton, EuiButtonProps, EuiLinkProps } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { sendTelemetry } from '../../../../shared/telemetry'; +import { HttpLogic } from '../../../../shared/http'; import { KibanaContext, IKibanaContext } from '../../../../index'; export const ProductButton: React.FC = () => { + const { http } = useValues(HttpLogic); const { externalUrl: { getWorkplaceSearchUrl }, - http, } = useContext(KibanaContext) as IKibanaContext; const buttonProps = { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_card.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_card.test.tsx index 1d7c565935e97..c890adb8ea043 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_card.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_card.test.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import '../../../__mocks__/kea.mock'; import '../../../__mocks__/shallow_usecontext.mock'; import React from 'react'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_card.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_card.tsx index 786357358dfa6..79be7ef1cb158 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_card.tsx @@ -5,6 +5,7 @@ */ import React, { useContext } from 'react'; +import { useValues } from 'kea'; import { EuiButton, @@ -17,7 +18,9 @@ import { EuiButtonEmptyProps, EuiLinkProps, } from '@elastic/eui'; + import { sendTelemetry } from '../../../shared/telemetry'; +import { HttpLogic } from '../../../shared/http'; import { KibanaContext, IKibanaContext } from '../../../index'; interface IOnboardingCardProps { @@ -39,8 +42,8 @@ export const OnboardingCard: React.FC = ({ actionPath, complete, }) => { + const { http } = useValues(HttpLogic); const { - http, externalUrl: { getWorkplaceSearchUrl }, } = useContext(KibanaContext) as IKibanaContext; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx index 0baadfc912ad5..079d981533e01 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx @@ -23,6 +23,7 @@ import { } from '@elastic/eui'; import sharedSourcesIcon from '../../components/shared/assets/share_circle.svg'; import { sendTelemetry } from '../../../shared/telemetry'; +import { HttpLogic } from '../../../shared/http'; import { KibanaContext, IKibanaContext } from '../../../index'; import { ORG_SOURCES_PATH, USERS_PATH, ORG_SETTINGS_PATH } from '../../routes'; @@ -135,8 +136,8 @@ export const OnboardingSteps: React.FC = () => { }; export const OrgNameOnboarding: React.FC = () => { + const { http } = useValues(HttpLogic); const { - http, externalUrl: { getWorkplaceSearchUrl }, } = useContext(KibanaContext) as IKibanaContext; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/recent_activity.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/recent_activity.tsx index 0813999c9a078..dd62e6de7c046 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/recent_activity.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/recent_activity.tsx @@ -14,6 +14,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { ContentSection } from '../../components/shared/content_section'; import { sendTelemetry } from '../../../shared/telemetry'; +import { HttpLogic } from '../../../shared/http'; import { KibanaContext, IKibanaContext } from '../../../index'; import { SOURCE_DETAILS_PATH, getContentSourcePath } from '../../routes'; @@ -93,8 +94,8 @@ export const RecentActivityItem: React.FC = ({ timestamp, sourceId, }) => { + const { http } = useValues(HttpLogic); const { - http, externalUrl: { getWorkplaceSearchUrl }, } = useContext(KibanaContext) as IKibanaContext; diff --git a/x-pack/plugins/enterprise_search/public/plugin.ts b/x-pack/plugins/enterprise_search/public/plugin.ts index c23bb23be3979..f59ec830c812f 100644 --- a/x-pack/plugins/enterprise_search/public/plugin.ts +++ b/x-pack/plugins/enterprise_search/public/plugin.ts @@ -7,7 +7,6 @@ import { AppMountParameters, CoreSetup, - CoreStart, HttpSetup, Plugin, PluginInitializerContext, @@ -17,7 +16,7 @@ import { FeatureCatalogueCategory, HomePublicPluginSetup, } from '../../../../src/plugins/home/public'; -import { LicensingPluginSetup } from '../../licensing/public'; +import { LicensingPluginStart } from '../../licensing/public'; import { APP_SEARCH_PLUGIN, ENTERPRISE_SEARCH_PLUGIN, @@ -36,7 +35,9 @@ export interface ClientData extends IInitialAppData { export interface PluginsSetup { home?: HomePublicPluginSetup; - licensing: LicensingPluginSetup; +} +export interface PluginsStart { + licensing: LicensingPluginStart; } export class EnterpriseSearchPlugin implements Plugin { @@ -57,16 +58,17 @@ export class EnterpriseSearchPlugin implements Plugin { appRoute: ENTERPRISE_SEARCH_PLUGIN.URL, category: DEFAULT_APP_CATEGORIES.enterpriseSearch, mount: async (params: AppMountParameters) => { - const [coreStart] = await core.getStartServices(); - const { chrome } = coreStart; - chrome.docTitle.change(ENTERPRISE_SEARCH_PLUGIN.NAME); + const kibanaDeps = await this.getKibanaDeps(core, params); + const pluginData = this.getPluginData(); - await this.getInitialData(coreStart.http); + const { chrome, http } = kibanaDeps.core; + chrome.docTitle.change(ENTERPRISE_SEARCH_PLUGIN.NAME); + await this.getInitialData(http); const { renderApp } = await import('./applications'); const { EnterpriseSearch } = await import('./applications/enterprise_search'); - return renderApp(EnterpriseSearch, params, coreStart, plugins, this.config, this.data); + return renderApp(EnterpriseSearch, kibanaDeps, pluginData); }, }); @@ -77,16 +79,17 @@ export class EnterpriseSearchPlugin implements Plugin { appRoute: APP_SEARCH_PLUGIN.URL, category: DEFAULT_APP_CATEGORIES.enterpriseSearch, mount: async (params: AppMountParameters) => { - const [coreStart] = await core.getStartServices(); - const { chrome } = coreStart; - chrome.docTitle.change(APP_SEARCH_PLUGIN.NAME); + const kibanaDeps = await this.getKibanaDeps(core, params); + const pluginData = this.getPluginData(); - await this.getInitialData(coreStart.http); + const { chrome, http } = kibanaDeps.core; + chrome.docTitle.change(APP_SEARCH_PLUGIN.NAME); + await this.getInitialData(http); const { renderApp } = await import('./applications'); const { AppSearch } = await import('./applications/app_search'); - return renderApp(AppSearch, params, coreStart, plugins, this.config, this.data); + return renderApp(AppSearch, kibanaDeps, pluginData); }, }); @@ -97,11 +100,12 @@ export class EnterpriseSearchPlugin implements Plugin { appRoute: WORKPLACE_SEARCH_PLUGIN.URL, category: DEFAULT_APP_CATEGORIES.enterpriseSearch, mount: async (params: AppMountParameters) => { - const [coreStart] = await core.getStartServices(); - const { chrome } = coreStart; - chrome.docTitle.change(WORKPLACE_SEARCH_PLUGIN.NAME); + const kibanaDeps = await this.getKibanaDeps(core, params); + const pluginData = this.getPluginData(); - await this.getInitialData(coreStart.http); + const { chrome, http } = kibanaDeps.core; + chrome.docTitle.change(APP_SEARCH_PLUGIN.NAME); + await this.getInitialData(http); const { renderApp, renderHeaderActions } = await import('./applications'); const { WorkplaceSearch } = await import('./applications/workplace_search'); @@ -113,7 +117,7 @@ export class EnterpriseSearchPlugin implements Plugin { renderHeaderActions(WorkplaceSearchHeaderActions, element, this.data.externalUrl) ); - return renderApp(WorkplaceSearch, params, coreStart, plugins, this.config, this.data); + return renderApp(WorkplaceSearch, kibanaDeps, pluginData); }, }); @@ -149,10 +153,22 @@ export class EnterpriseSearchPlugin implements Plugin { } } - public start(core: CoreStart) {} + public start() {} public stop() {} + private async getKibanaDeps(core: CoreSetup, params: AppMountParameters) { + // Helper for using start dependencies on mount (instead of setup dependencies) + // and for grouping Kibana-related args together (vs. plugin-specific args) + const [coreStart, pluginsStart] = await core.getStartServices(); + return { params, core: coreStart, plugins: pluginsStart as PluginsStart }; + } + + private getPluginData() { + // Small helper for grouping plugin data related args together + return { config: this.config, data: this.data }; + } + private async getInitialData(http: HttpSetup) { if (!this.config.host) return; // No API to call if (this.hasInitialized) return; // We've already made an initial call diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts index acddd3539965a..bd6f4b9da91fd 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts @@ -35,7 +35,7 @@ describe('Enterprise Search Telemetry API', () => { }); }); - describe('PUT /api/enterprise_search/telemetry', () => { + describe('PUT /api/enterprise_search/stats', () => { it('increments the saved objects counter for App Search', async () => { (incrementUICounter as jest.Mock).mockImplementation(jest.fn(() => successResponse)); diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.ts index bfc07c8b64ef5..8f6638ddc099e 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.ts @@ -25,7 +25,7 @@ export function registerTelemetryRoute({ }: IRouteDependencies) { router.put( { - path: '/api/enterprise_search/telemetry', + path: '/api/enterprise_search/stats', validate: { body: schema.object({ product: schema.oneOf([ diff --git a/x-pack/plugins/file_upload/public/util/indexing_service.js b/x-pack/plugins/file_upload/public/util/indexing_service.js index eb22b0228b48a..28cdb602455b5 100644 --- a/x-pack/plugins/file_upload/public/util/indexing_service.js +++ b/x-pack/plugins/file_upload/public/util/indexing_service.js @@ -189,19 +189,16 @@ async function chunkDataAndWriteToIndex({ id, index, data, mappings, settings }) } export async function createIndexPattern(indexPatternName) { - const indexPatterns = await indexPatternService.get(); try { - Object.assign(indexPatterns, { - id: '', - title: indexPatternName, - }); - - await indexPatterns.create(true); - const id = await getIndexPatternId(indexPatternName); - const indexPattern = await indexPatternService.get(id); + const indexPattern = await indexPatternService.createAndSave( + { + title: indexPatternName, + }, + true + ); return { success: true, - id, + id: indexPattern.id, fields: indexPattern.fields, }; } catch (error) { @@ -212,18 +209,6 @@ export async function createIndexPattern(indexPatternName) { } } -async function getIndexPatternId(name) { - const savedObjectSearch = await savedObjectsClient.find({ type: 'index-pattern', perPage: 1000 }); - const indexPatternSavedObjects = savedObjectSearch.savedObjects; - - if (indexPatternSavedObjects) { - const ip = indexPatternSavedObjects.find((i) => i.attributes.title === name); - return ip !== undefined ? ip.id : undefined; - } else { - return undefined; - } -} - export const getExistingIndexNames = async () => { const indexes = await httpService({ url: `/api/index_management/indices`, diff --git a/x-pack/plugins/index_management/server/plugin.ts b/x-pack/plugins/index_management/server/plugin.ts index 30aeeb6b45362..ae9633f3e22b9 100644 --- a/x-pack/plugins/index_management/server/plugin.ts +++ b/x-pack/plugins/index_management/server/plugin.ts @@ -84,7 +84,9 @@ export class IndexMgmtServerPlugin implements Plugin; + +export const anomalyTypeRT = rt.keyof({ + metrics_hosts: null, + metrics_k8s: null, +}); + +export type AnomalyType = rt.TypeOf; + +const sortOptionsRT = rt.keyof({ + anomalyScore: null, + dataset: null, + startTime: null, +}); + +const sortDirectionsRT = rt.keyof({ + asc: null, + desc: null, +}); + +const paginationPreviousPageCursorRT = rt.type({ + searchBefore: paginationCursorRT, +}); + +const paginationNextPageCursorRT = rt.type({ + searchAfter: paginationCursorRT, +}); + +export const paginationRT = rt.intersection([ + rt.type({ + pageSize: rt.number, + }), + rt.partial({ + cursor: rt.union([paginationPreviousPageCursorRT, paginationNextPageCursorRT]), + }), +]); + +export type Pagination = rt.TypeOf; + +export const sortRT = rt.type({ + field: sortOptionsRT, + direction: sortDirectionsRT, +}); + +export type Sort = rt.TypeOf; diff --git a/x-pack/plugins/security_solution/server/graphql/overview/index.ts b/x-pack/plugins/infra/common/http_api/infra_ml/results/index.ts similarity index 68% rename from x-pack/plugins/security_solution/server/graphql/overview/index.ts rename to x-pack/plugins/infra/common/http_api/infra_ml/results/index.ts index 58cf182ccd976..efd597a043e07 100644 --- a/x-pack/plugins/security_solution/server/graphql/overview/index.ts +++ b/x-pack/plugins/infra/common/http_api/infra_ml/results/index.ts @@ -4,5 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { createOverviewResolvers } from './resolvers'; -export { overviewSchema } from './schema.gql'; +export * from './metrics_hosts_anomalies'; +export * from './metrics_k8s_anomalies'; +export * from './common'; diff --git a/x-pack/plugins/infra/common/http_api/infra_ml/results/metrics_hosts_anomalies.ts b/x-pack/plugins/infra/common/http_api/infra_ml/results/metrics_hosts_anomalies.ts new file mode 100644 index 0000000000000..9fdac09fec20e --- /dev/null +++ b/x-pack/plugins/infra/common/http_api/infra_ml/results/metrics_hosts_anomalies.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; + +import { timeRangeRT, routeTimingMetadataRT } from '../../shared'; +import { anomalyTypeRT, paginationCursorRT, sortRT, paginationRT } from './common'; + +export const INFA_ML_GET_METRICS_HOSTS_ANOMALIES_PATH = + '/api/infra/infra_ml/results/metrics_hosts_anomalies'; + +const metricsHostAnomalyCommonFieldsRT = rt.type({ + id: rt.string, + anomalyScore: rt.number, + typical: rt.number, + actual: rt.number, + type: anomalyTypeRT, + duration: rt.number, + startTime: rt.number, + jobId: rt.string, +}); +const metricsHostsAnomalyRT = metricsHostAnomalyCommonFieldsRT; + +export type MetricsHostsAnomaly = rt.TypeOf; + +export const getMetricsHostsAnomaliesSuccessReponsePayloadRT = rt.intersection([ + rt.type({ + data: rt.intersection([ + rt.type({ + anomalies: rt.array(metricsHostsAnomalyRT), + // Signifies there are more entries backwards or forwards. If this was a request + // for a previous page, there are more previous pages, if this was a request for a next page, + // there are more next pages. + hasMoreEntries: rt.boolean, + }), + rt.partial({ + paginationCursors: rt.type({ + // The cursor to use to fetch the previous page + previousPageCursor: paginationCursorRT, + // The cursor to use to fetch the next page + nextPageCursor: paginationCursorRT, + }), + }), + ]), + }), + rt.partial({ + timing: routeTimingMetadataRT, + }), +]); + +export type GetMetricsHostsAnomaliesSuccessResponsePayload = rt.TypeOf< + typeof getMetricsHostsAnomaliesSuccessReponsePayloadRT +>; + +export const getMetricsHostsAnomaliesRequestPayloadRT = rt.type({ + data: rt.intersection([ + rt.type({ + // the ID of the source configuration + sourceId: rt.string, + // the time range to fetch the log entry anomalies from + timeRange: timeRangeRT, + }), + rt.partial({ + // Pagination properties + pagination: paginationRT, + // Sort properties + sort: sortRT, + // // Dataset filters + // datasets: rt.array(rt.string), + }), + ]), +}); + +export type GetMetricsHostsAnomaliesRequestPayload = rt.TypeOf< + typeof getMetricsHostsAnomaliesRequestPayloadRT +>; diff --git a/x-pack/plugins/infra/common/http_api/infra_ml/results/metrics_k8s_anomalies.ts b/x-pack/plugins/infra/common/http_api/infra_ml/results/metrics_k8s_anomalies.ts new file mode 100644 index 0000000000000..ab1f245a74c0c --- /dev/null +++ b/x-pack/plugins/infra/common/http_api/infra_ml/results/metrics_k8s_anomalies.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; + +import { timeRangeRT, routeTimingMetadataRT } from '../../shared'; +import { paginationCursorRT, anomalyTypeRT, sortRT, paginationRT } from './common'; + +export const INFA_ML_GET_METRICS_K8S_ANOMALIES_PATH = + '/api/infra/infra_ml/results/metrics_k8s_anomalies'; + +const metricsK8sAnomalyCommonFieldsRT = rt.type({ + id: rt.string, + anomalyScore: rt.number, + typical: rt.number, + actual: rt.number, + type: anomalyTypeRT, + duration: rt.number, + startTime: rt.number, + jobId: rt.string, +}); +const metricsK8sAnomalyRT = metricsK8sAnomalyCommonFieldsRT; + +export type MetricsK8sAnomaly = rt.TypeOf; + +export const getMetricsK8sAnomaliesSuccessReponsePayloadRT = rt.intersection([ + rt.type({ + data: rt.intersection([ + rt.type({ + anomalies: rt.array(metricsK8sAnomalyRT), + // Signifies there are more entries backwards or forwards. If this was a request + // for a previous page, there are more previous pages, if this was a request for a next page, + // there are more next pages. + hasMoreEntries: rt.boolean, + }), + rt.partial({ + paginationCursors: rt.type({ + // The cursor to use to fetch the previous page + previousPageCursor: paginationCursorRT, + // The cursor to use to fetch the next page + nextPageCursor: paginationCursorRT, + }), + }), + ]), + }), + rt.partial({ + timing: routeTimingMetadataRT, + }), +]); + +export type GetMetricsK8sAnomaliesSuccessResponsePayload = rt.TypeOf< + typeof getMetricsK8sAnomaliesSuccessReponsePayloadRT +>; + +export const getMetricsK8sAnomaliesRequestPayloadRT = rt.type({ + data: rt.intersection([ + rt.type({ + // the ID of the source configuration + sourceId: rt.string, + // the time range to fetch the log entry anomalies from + timeRange: timeRangeRT, + }), + rt.partial({ + // Pagination properties + pagination: paginationRT, + // Sort properties + sort: sortRT, + // Dataset filters + datasets: rt.array(rt.string), + }), + ]), +}); + +export type GetMetricsK8sAnomaliesRequestPayload = rt.TypeOf< + typeof getMetricsK8sAnomaliesRequestPayloadRT +>; diff --git a/x-pack/plugins/infra/common/http_api/snapshot_api.ts b/x-pack/plugins/infra/common/http_api/snapshot_api.ts index e1b8dfa4770ba..a6273fa967baf 100644 --- a/x-pack/plugins/infra/common/http_api/snapshot_api.ts +++ b/x-pack/plugins/infra/common/http_api/snapshot_api.ts @@ -99,7 +99,7 @@ export const SnapshotRequestRT = rt.intersection([ rt.type({ timerange: InfraTimerangeInputRT, metrics: rt.array(SnapshotMetricInputRT), - groupBy: SnapshotGroupByRT, + groupBy: rt.union([SnapshotGroupByRT, rt.null]), nodeType: ItemTypeRT, sourceId: rt.string, }), diff --git a/x-pack/plugins/infra/common/infra_ml/anomaly_results.ts b/x-pack/plugins/infra/common/infra_ml/anomaly_results.ts new file mode 100644 index 0000000000000..f4497dbba5056 --- /dev/null +++ b/x-pack/plugins/infra/common/infra_ml/anomaly_results.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const ML_SEVERITY_SCORES = { + warning: 3, + minor: 25, + major: 50, + critical: 75, +}; + +export type MLSeverityScoreCategories = keyof typeof ML_SEVERITY_SCORES; + +export const ML_SEVERITY_COLORS = { + critical: 'rgb(228, 72, 72)', + major: 'rgb(229, 113, 0)', + minor: 'rgb(255, 221, 0)', + warning: 'rgb(125, 180, 226)', +}; + +export const getSeverityCategoryForScore = ( + score: number +): MLSeverityScoreCategories | undefined => { + if (score >= ML_SEVERITY_SCORES.critical) { + return 'critical'; + } else if (score >= ML_SEVERITY_SCORES.major) { + return 'major'; + } else if (score >= ML_SEVERITY_SCORES.minor) { + return 'minor'; + } else if (score >= ML_SEVERITY_SCORES.warning) { + return 'warning'; + } else { + // Category is too low to include + return undefined; + } +}; + +export const formatAnomalyScore = (score: number) => { + return Math.round(score); +}; + +export const formatOneDecimalPlace = (number: number) => { + return Math.round(number * 10) / 10; +}; + +export const getFriendlyNameForPartitionId = (partitionId: string) => { + return partitionId !== '' ? partitionId : 'unknown'; +}; + +export const compareDatasetsByMaximumAnomalyScore = < + Dataset extends { maximumAnomalyScore: number } +>( + firstDataset: Dataset, + secondDataset: Dataset +) => firstDataset.maximumAnomalyScore - secondDataset.maximumAnomalyScore; diff --git a/x-pack/plugins/infra/common/infra_ml/index.ts b/x-pack/plugins/infra/common/infra_ml/index.ts new file mode 100644 index 0000000000000..88fbbd4f25045 --- /dev/null +++ b/x-pack/plugins/infra/common/infra_ml/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './infra_ml'; +export * from './anomaly_results'; +export * from './job_parameters'; +export * from './metrics_hosts_ml'; +export * from './metrics_k8s_ml'; diff --git a/x-pack/plugins/infra/common/infra_ml/infra_ml.ts b/x-pack/plugins/infra/common/infra_ml/infra_ml.ts new file mode 100644 index 0000000000000..680a2a0fef114 --- /dev/null +++ b/x-pack/plugins/infra/common/infra_ml/infra_ml.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// combines and abstracts job and datafeed status +export type JobStatus = + | 'unknown' + | 'missing' + | 'initializing' + | 'stopped' + | 'started' + | 'finished' + | 'failed'; + +export type SetupStatus = + | { type: 'initializing' } // acquiring job statuses to determine setup status + | { type: 'unknown' } // job status could not be acquired (failed request etc) + | { type: 'required' } // setup required + | { type: 'pending' } // In the process of setting up the module for the first time or retrying, waiting for response + | { type: 'succeeded' } // setup succeeded, notifying user + | { + type: 'failed'; + reasons: string[]; + } // setup failed, notifying user + | { + type: 'skipped'; + newlyCreated?: boolean; + }; // setup is not necessary + +/** + * Maps a job status to the possibility that results have already been produced + * before this state was reached. + */ +export const isJobStatusWithResults = (jobStatus: JobStatus) => + ['started', 'finished', 'stopped', 'failed'].includes(jobStatus); + +export const isHealthyJobStatus = (jobStatus: JobStatus) => + ['started', 'finished'].includes(jobStatus); + +/** + * Maps a setup status to the possibility that results have already been + * produced before this state was reached. + */ +export const isSetupStatusWithResults = (setupStatus: SetupStatus) => + setupStatus.type === 'skipped'; + +const KIBANA_SAMPLE_DATA_INDICES = ['kibana_sample_data_logs*']; + +export const isExampleDataIndex = (indexName: string) => + KIBANA_SAMPLE_DATA_INDICES.includes(indexName); diff --git a/x-pack/plugins/infra/common/infra_ml/job_parameters.ts b/x-pack/plugins/infra/common/infra_ml/job_parameters.ts new file mode 100644 index 0000000000000..8cd1c9ea6e2ba --- /dev/null +++ b/x-pack/plugins/infra/common/infra_ml/job_parameters.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; + +export const bucketSpan = 900000; + +export const categoriesMessageField = 'message'; + +export const partitionField = 'event.dataset'; + +export const getJobIdPrefix = (spaceId: string, sourceId: string) => + `kibana-metrics-ui-${spaceId}-${sourceId}-`; + +export const getJobId = (spaceId: string, sourceId: string, jobType: string) => + `${getJobIdPrefix(spaceId, sourceId)}${jobType}`; + +export const getDatafeedId = (spaceId: string, sourceId: string, jobType: string) => + `datafeed-${getJobId(spaceId, sourceId, jobType)}`; + +export const datasetFilterRT = rt.union([ + rt.strict({ + type: rt.literal('includeAll'), + }), + rt.strict({ + type: rt.literal('includeSome'), + datasets: rt.array(rt.string), + }), +]); + +export type DatasetFilter = rt.TypeOf; + +export const jobSourceConfigurationRT = rt.partial({ + indexPattern: rt.string, + timestampField: rt.string, + bucketSpan: rt.number, + datasetFilter: datasetFilterRT, +}); + +export type JobSourceConfiguration = rt.TypeOf; + +export const jobCustomSettingsRT = rt.partial({ + job_revision: rt.number, + metrics_source_config: jobSourceConfigurationRT, +}); + +export type JobCustomSettings = rt.TypeOf; + +export const combineDatasetFilters = ( + firstFilter: DatasetFilter, + secondFilter: DatasetFilter +): DatasetFilter => { + if (firstFilter.type === 'includeAll' && secondFilter.type === 'includeAll') { + return { + type: 'includeAll', + }; + } + + const includedDatasets = new Set([ + ...(firstFilter.type === 'includeSome' ? firstFilter.datasets : []), + ...(secondFilter.type === 'includeSome' ? secondFilter.datasets : []), + ]); + + return { + type: 'includeSome', + datasets: [...includedDatasets], + }; +}; + +export const filterDatasetFilter = ( + datasetFilter: DatasetFilter, + predicate: (dataset: string) => boolean +): DatasetFilter => { + if (datasetFilter.type === 'includeAll') { + return datasetFilter; + } else { + const newDatasets = datasetFilter.datasets.filter(predicate); + + if (newDatasets.length > 0) { + return { + type: 'includeSome', + datasets: newDatasets, + }; + } else { + return { + type: 'includeAll', + }; + } + } +}; diff --git a/x-pack/plugins/infra/common/infra_ml/metrics_hosts_ml.ts b/x-pack/plugins/infra/common/infra_ml/metrics_hosts_ml.ts new file mode 100644 index 0000000000000..d09b3be78204f --- /dev/null +++ b/x-pack/plugins/infra/common/infra_ml/metrics_hosts_ml.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; + +export const metricsHostsJobTypeRT = rt.keyof({ + hosts_memory_usage: null, + hosts_network_in: null, + hosts_network_out: null, +}); + +export type MetricsHostsJobType = rt.TypeOf; + +export const metricsHostsJobTypes: MetricsHostsJobType[] = [ + 'hosts_memory_usage', + 'hosts_network_in', + 'hosts_network_out', +]; diff --git a/x-pack/plugins/infra/common/infra_ml/metrics_k8s_ml.ts b/x-pack/plugins/infra/common/infra_ml/metrics_k8s_ml.ts new file mode 100644 index 0000000000000..3c27dbb61a14a --- /dev/null +++ b/x-pack/plugins/infra/common/infra_ml/metrics_k8s_ml.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; + +export const metricK8sJobTypeRT = rt.keyof({ + k8s_memory_usage: null, + k8s_network_in: null, + k8s_network_out: null, +}); + +export type MetricK8sJobType = rt.TypeOf; + +export const metricsK8SJobTypes: MetricK8sJobType[] = [ + 'k8s_memory_usage', + 'k8s_network_in', + 'k8s_network_out', +]; diff --git a/x-pack/plugins/infra/common/inventory_models/aws_ec2/index.ts b/x-pack/plugins/infra/common/inventory_models/aws_ec2/index.ts index c12137f7810d4..6453332be4f50 100644 --- a/x-pack/plugins/infra/common/inventory_models/aws_ec2/index.ts +++ b/x-pack/plugins/infra/common/inventory_models/aws_ec2/index.ts @@ -31,4 +31,5 @@ export const awsEC2: InventoryModel = { }, requiredMetrics: ['awsEC2CpuUtilization', 'awsEC2NetworkTraffic', 'awsEC2DiskIOBytes'], tooltipMetrics: ['cpu', 'rx', 'tx'], + nodeFilter: [{ term: { 'event.dataset': 'aws.ec2' } }], }; diff --git a/x-pack/plugins/infra/common/inventory_models/intl_strings.ts b/x-pack/plugins/infra/common/inventory_models/intl_strings.ts index 2a885136f4ee7..cd7409100160d 100644 --- a/x-pack/plugins/infra/common/inventory_models/intl_strings.ts +++ b/x-pack/plugins/infra/common/inventory_models/intl_strings.ts @@ -5,36 +5,8 @@ */ import { i18n } from '@kbn/i18n'; -import { SnapshotMetricType } from './types'; -export const CPUUsage = i18n.translate('xpack.infra.waffle.metricOptions.cpuUsageText', { - defaultMessage: 'CPU usage', -}); - -export const MemoryUsage = i18n.translate('xpack.infra.waffle.metricOptions.memoryUsageText', { - defaultMessage: 'Memory usage', -}); - -export const InboundTraffic = i18n.translate( - 'xpack.infra.waffle.metricOptions.inboundTrafficText', - { - defaultMessage: 'Inbound traffic', - } -); - -export const OutboundTraffic = i18n.translate( - 'xpack.infra.waffle.metricOptions.outboundTrafficText', - { - defaultMessage: 'Outbound traffic', - } -); - -export const LogRate = i18n.translate('xpack.infra.waffle.metricOptions.hostLogRateText', { - defaultMessage: 'Log rate', -}); - -export const Load = i18n.translate('xpack.infra.waffle.metricOptions.loadText', { - defaultMessage: 'Load', -}); +import { toMetricOpt } from '../snapshot_metric_i18n'; +import { SnapshotMetricType, SnapshotMetricTypeKeys } from './types'; interface Lookup { [id: string]: string; @@ -70,80 +42,9 @@ export const fieldToName = (field: string) => { return LOOKUP[field] || field; }; -export const SNAPSHOT_METRIC_TRANSLATIONS = { - cpu: i18n.translate('xpack.infra.waffle.metricOptions.cpuUsageText', { - defaultMessage: 'CPU usage', - }), - - memory: i18n.translate('xpack.infra.waffle.metricOptions.memoryUsageText', { - defaultMessage: 'Memory usage', - }), - - rx: i18n.translate('xpack.infra.waffle.metricOptions.inboundTrafficText', { - defaultMessage: 'Inbound traffic', - }), - - tx: i18n.translate('xpack.infra.waffle.metricOptions.outboundTrafficText', { - defaultMessage: 'Outbound traffic', - }), - - logRate: i18n.translate('xpack.infra.waffle.metricOptions.hostLogRateText', { - defaultMessage: 'Log rate', - }), - - load: i18n.translate('xpack.infra.waffle.metricOptions.loadText', { - defaultMessage: 'Load', - }), - - count: i18n.translate('xpack.infra.waffle.metricOptions.countText', { - defaultMessage: 'Count', - }), - diskIOReadBytes: i18n.translate('xpack.infra.waffle.metricOptions.diskIOReadBytes', { - defaultMessage: 'Disk Reads', - }), - diskIOWriteBytes: i18n.translate('xpack.infra.waffle.metricOptions.diskIOWriteBytes', { - defaultMessage: 'Disk Writes', - }), - s3BucketSize: i18n.translate('xpack.infra.waffle.metricOptions.s3BucketSize', { - defaultMessage: 'Bucket Size', - }), - s3TotalRequests: i18n.translate('xpack.infra.waffle.metricOptions.s3TotalRequests', { - defaultMessage: 'Total Requests', - }), - s3NumberOfObjects: i18n.translate('xpack.infra.waffle.metricOptions.s3NumberOfObjects', { - defaultMessage: 'Number of Objects', - }), - s3DownloadBytes: i18n.translate('xpack.infra.waffle.metricOptions.s3DownloadBytes', { - defaultMessage: 'Downloads (Bytes)', - }), - s3UploadBytes: i18n.translate('xpack.infra.waffle.metricOptions.s3UploadBytes', { - defaultMessage: 'Uploads (Bytes)', - }), - rdsConnections: i18n.translate('xpack.infra.waffle.metricOptions.rdsConnections', { - defaultMessage: 'Connections', - }), - rdsQueriesExecuted: i18n.translate('xpack.infra.waffle.metricOptions.rdsQueriesExecuted', { - defaultMessage: 'Queries Executed', - }), - rdsActiveTransactions: i18n.translate('xpack.infra.waffle.metricOptions.rdsActiveTransactions', { - defaultMessage: 'Active Transactions', - }), - rdsLatency: i18n.translate('xpack.infra.waffle.metricOptions.rdsLatency', { - defaultMessage: 'Latency', - }), - sqsMessagesVisible: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesVisible', { - defaultMessage: 'Messages Available', - }), - sqsMessagesDelayed: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesDelayed', { - defaultMessage: 'Messages Delayed', - }), - sqsMessagesSent: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesSent', { - defaultMessage: 'Messages Added', - }), - sqsMessagesEmpty: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesEmpty', { - defaultMessage: 'Messages Returned Empty', - }), - sqsOldestMessage: i18n.translate('xpack.infra.waffle.metricOptions.sqsOldestMessage', { - defaultMessage: 'Oldest Message', - }), -} as Record; +const snapshotTypeKeys = Object.keys(SnapshotMetricTypeKeys) as SnapshotMetricType[]; +export const SNAPSHOT_METRIC_TRANSLATIONS = snapshotTypeKeys.reduce((result, metric) => { + const text = toMetricOpt(metric)?.text; + if (text) return { ...result, [metric]: text }; + return result; +}, {}) as Record; diff --git a/x-pack/plugins/infra/common/inventory_models/types.ts b/x-pack/plugins/infra/common/inventory_models/types.ts index 851646ef1fa12..5cc788f238365 100644 --- a/x-pack/plugins/infra/common/inventory_models/types.ts +++ b/x-pack/plugins/infra/common/inventory_models/types.ts @@ -314,7 +314,7 @@ export const ESAggregationRT = rt.union([ export const MetricsUIAggregationRT = rt.record(rt.string, ESAggregationRT); export type MetricsUIAggregation = rt.TypeOf; -export const SnapshotMetricTypeRT = rt.keyof({ +export const SnapshotMetricTypeKeys = { count: null, cpu: null, load: null, @@ -339,7 +339,8 @@ export const SnapshotMetricTypeRT = rt.keyof({ sqsMessagesEmpty: null, sqsOldestMessage: null, custom: null, -}); +}; +export const SnapshotMetricTypeRT = rt.keyof(SnapshotMetricTypeKeys); export type SnapshotMetricType = rt.TypeOf; @@ -370,4 +371,5 @@ export interface InventoryModel { metrics: InventoryMetrics; requiredMetrics: InventoryMetric[]; tooltipMetrics: SnapshotMetricType[]; + nodeFilter?: object[]; } diff --git a/x-pack/plugins/infra/common/snapshot_metric_i18n.ts b/x-pack/plugins/infra/common/snapshot_metric_i18n.ts index 412c60fd9a1a7..60454e770584e 100644 --- a/x-pack/plugins/infra/common/snapshot_metric_i18n.ts +++ b/x-pack/plugins/infra/common/snapshot_metric_i18n.ts @@ -4,204 +4,235 @@ * you may not use this file except in compliance with the Elastic License. */ import { i18n } from '@kbn/i18n'; +import { mapValues } from 'lodash'; import { SnapshotMetricType } from './inventory_models/types'; -const Translations = { +// Lowercase versions of all metrics, for when they need to be used in the middle of a sentence; +// these may need to be translated differently depending on language, e.g. still capitalizing "CPU" +const TranslationsLowercase = { CPUUsage: i18n.translate('xpack.infra.waffle.metricOptions.cpuUsageText', { defaultMessage: 'CPU usage', }), MemoryUsage: i18n.translate('xpack.infra.waffle.metricOptions.memoryUsageText', { - defaultMessage: 'Memory usage', + defaultMessage: 'memory usage', }), InboundTraffic: i18n.translate('xpack.infra.waffle.metricOptions.inboundTrafficText', { - defaultMessage: 'Inbound traffic', + defaultMessage: 'inbound traffic', }), OutboundTraffic: i18n.translate('xpack.infra.waffle.metricOptions.outboundTrafficText', { - defaultMessage: 'Outbound traffic', + defaultMessage: 'outbound traffic', }), LogRate: i18n.translate('xpack.infra.waffle.metricOptions.hostLogRateText', { - defaultMessage: 'Log rate', + defaultMessage: 'log rate', }), Load: i18n.translate('xpack.infra.waffle.metricOptions.loadText', { - defaultMessage: 'Load', + defaultMessage: 'load', }), Count: i18n.translate('xpack.infra.waffle.metricOptions.countText', { - defaultMessage: 'Count', + defaultMessage: 'count', }), DiskIOReadBytes: i18n.translate('xpack.infra.waffle.metricOptions.diskIOReadBytes', { - defaultMessage: 'Disk Reads', + defaultMessage: 'disk reads', }), DiskIOWriteBytes: i18n.translate('xpack.infra.waffle.metricOptions.diskIOWriteBytes', { - defaultMessage: 'Disk Writes', + defaultMessage: 'disk writes', }), s3BucketSize: i18n.translate('xpack.infra.waffle.metricOptions.s3BucketSize', { - defaultMessage: 'Bucket Size', + defaultMessage: 'bucket size', }), s3TotalRequests: i18n.translate('xpack.infra.waffle.metricOptions.s3TotalRequests', { - defaultMessage: 'Total Requests', + defaultMessage: 'total requests', }), s3NumberOfObjects: i18n.translate('xpack.infra.waffle.metricOptions.s3NumberOfObjects', { - defaultMessage: 'Number of Objects', + defaultMessage: 'number of objects', }), s3DownloadBytes: i18n.translate('xpack.infra.waffle.metricOptions.s3DownloadBytes', { - defaultMessage: 'Downloads (Bytes)', + defaultMessage: 'downloads (bytes)', }), s3UploadBytes: i18n.translate('xpack.infra.waffle.metricOptions.s3UploadBytes', { - defaultMessage: 'Uploads (Bytes)', + defaultMessage: 'uploads (bytes)', }), rdsConnections: i18n.translate('xpack.infra.waffle.metricOptions.rdsConnections', { - defaultMessage: 'Connections', + defaultMessage: 'connections', }), rdsQueriesExecuted: i18n.translate('xpack.infra.waffle.metricOptions.rdsQueriesExecuted', { - defaultMessage: 'Queries Executed', + defaultMessage: 'queries executed', }), rdsActiveTransactions: i18n.translate('xpack.infra.waffle.metricOptions.rdsActiveTransactions', { - defaultMessage: 'Active Transactions', + defaultMessage: 'active transactions', }), rdsLatency: i18n.translate('xpack.infra.waffle.metricOptions.rdsLatency', { - defaultMessage: 'Latency', + defaultMessage: 'latency', }), sqsMessagesVisible: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesVisible', { - defaultMessage: 'Messages Available', + defaultMessage: 'messages available', }), sqsMessagesDelayed: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesDelayed', { - defaultMessage: 'Messages Delayed', + defaultMessage: 'messages delayed', }), sqsMessagesSent: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesSent', { - defaultMessage: 'Messages Added', + defaultMessage: 'messages added', }), sqsMessagesEmpty: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesEmpty', { - defaultMessage: 'Messages Returned Empty', + defaultMessage: 'messages returned empty', }), sqsOldestMessage: i18n.translate('xpack.infra.waffle.metricOptions.sqsOldestMessage', { - defaultMessage: 'Oldest Message', + defaultMessage: 'oldest message', }), }; +const Translations = mapValues( + TranslationsLowercase, + (translation) => `${translation[0].toUpperCase()}${translation.slice(1)}` +); + export const toMetricOpt = ( metric: SnapshotMetricType -): { text: string; value: SnapshotMetricType } | undefined => { +): { text: string; textLC: string; value: SnapshotMetricType } | undefined => { switch (metric) { case 'cpu': return { text: Translations.CPUUsage, + textLC: TranslationsLowercase.CPUUsage, value: 'cpu', }; case 'memory': return { text: Translations.MemoryUsage, + textLC: TranslationsLowercase.MemoryUsage, value: 'memory', }; case 'rx': return { text: Translations.InboundTraffic, + textLC: TranslationsLowercase.InboundTraffic, value: 'rx', }; case 'tx': return { text: Translations.OutboundTraffic, + textLC: TranslationsLowercase.OutboundTraffic, value: 'tx', }; case 'logRate': return { text: Translations.LogRate, + textLC: TranslationsLowercase.LogRate, value: 'logRate', }; case 'load': return { text: Translations.Load, + textLC: TranslationsLowercase.Load, value: 'load', }; case 'count': return { text: Translations.Count, + textLC: TranslationsLowercase.Count, value: 'count', }; case 'diskIOReadBytes': return { text: Translations.DiskIOReadBytes, + textLC: TranslationsLowercase.DiskIOReadBytes, value: 'diskIOReadBytes', }; case 'diskIOWriteBytes': return { text: Translations.DiskIOWriteBytes, + textLC: TranslationsLowercase.DiskIOWriteBytes, value: 'diskIOWriteBytes', }; case 's3BucketSize': return { text: Translations.s3BucketSize, + textLC: TranslationsLowercase.s3BucketSize, value: 's3BucketSize', }; case 's3TotalRequests': return { text: Translations.s3TotalRequests, + textLC: TranslationsLowercase.s3TotalRequests, value: 's3TotalRequests', }; case 's3NumberOfObjects': return { text: Translations.s3NumberOfObjects, + textLC: TranslationsLowercase.s3NumberOfObjects, value: 's3NumberOfObjects', }; case 's3DownloadBytes': return { text: Translations.s3DownloadBytes, + textLC: TranslationsLowercase.s3DownloadBytes, value: 's3DownloadBytes', }; case 's3UploadBytes': return { text: Translations.s3UploadBytes, + textLC: TranslationsLowercase.s3UploadBytes, value: 's3UploadBytes', }; case 'rdsConnections': return { text: Translations.rdsConnections, + textLC: TranslationsLowercase.rdsConnections, value: 'rdsConnections', }; case 'rdsQueriesExecuted': return { text: Translations.rdsQueriesExecuted, + textLC: TranslationsLowercase.rdsQueriesExecuted, value: 'rdsQueriesExecuted', }; case 'rdsActiveTransactions': return { text: Translations.rdsActiveTransactions, + textLC: TranslationsLowercase.rdsActiveTransactions, value: 'rdsActiveTransactions', }; case 'rdsLatency': return { text: Translations.rdsLatency, + textLC: TranslationsLowercase.rdsLatency, value: 'rdsLatency', }; case 'sqsMessagesVisible': return { text: Translations.sqsMessagesVisible, + textLC: TranslationsLowercase.sqsMessagesVisible, value: 'sqsMessagesVisible', }; case 'sqsMessagesDelayed': return { text: Translations.sqsMessagesDelayed, + textLC: TranslationsLowercase.sqsMessagesDelayed, value: 'sqsMessagesDelayed', }; case 'sqsMessagesSent': return { text: Translations.sqsMessagesSent, + textLC: TranslationsLowercase.sqsMessagesSent, value: 'sqsMessagesSent', }; case 'sqsMessagesEmpty': return { text: Translations.sqsMessagesEmpty, + textLC: TranslationsLowercase.sqsMessagesEmpty, value: 'sqsMessagesEmpty', }; case 'sqsOldestMessage': return { text: Translations.sqsOldestMessage, + textLC: TranslationsLowercase.sqsOldestMessage, value: 'sqsOldestMessage', }; } diff --git a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx index 83fe233553351..698b0d3ad0caf 100644 --- a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx @@ -13,10 +13,9 @@ import { EuiDescriptionListTitle, EuiDescriptionListDescription, } from '@elastic/eui'; -import { EuiPopover } from '@elastic/eui'; +import { EuiPopover, EuiLink } from '@elastic/eui'; import { EuiListGroup, EuiListGroupItem } from '@elastic/eui'; import { EuiFlexItem } from '@elastic/eui'; -import { EuiButtonIcon } from '@elastic/eui'; import { SavedViewCreateModal } from './create_modal'; import { SavedViewUpdateModal } from './update_modal'; import { SavedViewManageViewsFlyout } from './manage_views_flyout'; @@ -151,15 +150,6 @@ export function SavedViewsToolbarControls(props: Props) { - - - (props: Props) { id="xpack.infra.savedView.currentView" /> - - {currentView - ? currentView.name - : i18n.translate('xpack.infra.savedView.unknownView', { - defaultMessage: 'No view selected', - })} - + + + {currentView + ? currentView.name + : i18n.translate('xpack.infra.savedView.unknownView', { + defaultMessage: 'No view selected', + })} + + diff --git a/x-pack/plugins/infra/public/containers/ml/api/ml_api_types.ts b/x-pack/plugins/infra/public/containers/ml/api/ml_api_types.ts new file mode 100644 index 0000000000000..ee70edc31d49b --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/api/ml_api_types.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; + +export const getMlCapabilitiesResponsePayloadRT = rt.type({ + capabilities: rt.type({ + canGetJobs: rt.boolean, + canCreateJob: rt.boolean, + canDeleteJob: rt.boolean, + canOpenJob: rt.boolean, + canCloseJob: rt.boolean, + canForecastJob: rt.boolean, + canGetDatafeeds: rt.boolean, + canStartStopDatafeed: rt.boolean, + canUpdateJob: rt.boolean, + canUpdateDatafeed: rt.boolean, + canPreviewDatafeed: rt.boolean, + }), + isPlatinumOrTrialLicense: rt.boolean, + mlFeatureEnabledInSpace: rt.boolean, + upgradeInProgress: rt.boolean, +}); + +export type GetMlCapabilitiesResponsePayload = rt.TypeOf; diff --git a/x-pack/plugins/infra/public/containers/ml/api/ml_cleanup.ts b/x-pack/plugins/infra/public/containers/ml/api/ml_cleanup.ts new file mode 100644 index 0000000000000..23fa338e74f14 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/api/ml_cleanup.ts @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; +import { npStart } from '../../../legacy_singletons'; + +import { getDatafeedId, getJobId } from '../../../../common/infra_ml'; +import { throwErrors, createPlainError } from '../../../../common/runtime_types'; + +export const callDeleteJobs = async ( + spaceId: string, + sourceId: string, + jobTypes: JobType[] +) => { + // NOTE: Deleting the jobs via this API will delete the datafeeds at the same time + const deleteJobsResponse = await npStart.http.fetch('/api/ml/jobs/delete_jobs', { + method: 'POST', + body: JSON.stringify( + deleteJobsRequestPayloadRT.encode({ + jobIds: jobTypes.map((jobType) => getJobId(spaceId, sourceId, jobType)), + }) + ), + }); + + return pipe( + deleteJobsResponsePayloadRT.decode(deleteJobsResponse), + fold(throwErrors(createPlainError), identity) + ); +}; + +export const callGetJobDeletionTasks = async () => { + const jobDeletionTasksResponse = await npStart.http.fetch('/api/ml/jobs/deleting_jobs_tasks'); + + return pipe( + getJobDeletionTasksResponsePayloadRT.decode(jobDeletionTasksResponse), + fold(throwErrors(createPlainError), identity) + ); +}; + +export const callStopDatafeeds = async ( + spaceId: string, + sourceId: string, + jobTypes: JobType[] +) => { + // Stop datafeed due to https://github.com/elastic/kibana/issues/44652 + const stopDatafeedResponse = await npStart.http.fetch('/api/ml/jobs/stop_datafeeds', { + method: 'POST', + body: JSON.stringify( + stopDatafeedsRequestPayloadRT.encode({ + datafeedIds: jobTypes.map((jobType) => getDatafeedId(spaceId, sourceId, jobType)), + }) + ), + }); + + return pipe( + stopDatafeedsResponsePayloadRT.decode(stopDatafeedResponse), + fold(throwErrors(createPlainError), identity) + ); +}; + +export const deleteJobsRequestPayloadRT = rt.type({ + jobIds: rt.array(rt.string), +}); + +export type DeleteJobsRequestPayload = rt.TypeOf; + +export const deleteJobsResponsePayloadRT = rt.record( + rt.string, + rt.type({ + deleted: rt.boolean, + }) +); + +export type DeleteJobsResponsePayload = rt.TypeOf; + +export const getJobDeletionTasksResponsePayloadRT = rt.type({ + jobIds: rt.array(rt.string), +}); + +export const stopDatafeedsRequestPayloadRT = rt.type({ + datafeedIds: rt.array(rt.string), +}); + +export const stopDatafeedsResponsePayloadRT = rt.record( + rt.string, + rt.type({ + stopped: rt.boolean, + }) +); diff --git a/x-pack/plugins/infra/public/containers/ml/api/ml_get_jobs_summary_api.ts b/x-pack/plugins/infra/public/containers/ml/api/ml_get_jobs_summary_api.ts new file mode 100644 index 0000000000000..3fddb63f69791 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/api/ml_get_jobs_summary_api.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; +import { pipe } from 'fp-ts/lib/pipeable'; +import * as rt from 'io-ts'; +import { npStart } from '../../../legacy_singletons'; + +import { getJobId, jobCustomSettingsRT } from '../../../../common/infra_ml'; +import { createPlainError, throwErrors } from '../../../../common/runtime_types'; + +export const callJobsSummaryAPI = async ( + spaceId: string, + sourceId: string, + jobTypes: JobType[] +) => { + const response = await npStart.http.fetch('/api/ml/jobs/jobs_summary', { + method: 'POST', + body: JSON.stringify( + fetchJobStatusRequestPayloadRT.encode({ + jobIds: jobTypes.map((jobType) => getJobId(spaceId, sourceId, jobType)), + }) + ), + }); + return pipe( + fetchJobStatusResponsePayloadRT.decode(response), + fold(throwErrors(createPlainError), identity) + ); +}; + +export const fetchJobStatusRequestPayloadRT = rt.type({ + jobIds: rt.array(rt.string), +}); + +export type FetchJobStatusRequestPayload = rt.TypeOf; + +const datafeedStateRT = rt.keyof({ + started: null, + stopped: null, + stopping: null, + '': null, +}); + +const jobStateRT = rt.keyof({ + closed: null, + closing: null, + deleting: null, + failed: null, + opened: null, + opening: null, +}); + +const jobCategorizationStatusRT = rt.keyof({ + ok: null, + warn: null, +}); + +const jobModelSizeStatsRT = rt.type({ + categorization_status: jobCategorizationStatusRT, + categorized_doc_count: rt.number, + dead_category_count: rt.number, + frequent_category_count: rt.number, + rare_category_count: rt.number, + total_category_count: rt.number, +}); + +export type JobModelSizeStats = rt.TypeOf; + +export const jobSummaryRT = rt.intersection([ + rt.type({ + id: rt.string, + jobState: jobStateRT, + }), + rt.partial({ + datafeedIndices: rt.array(rt.string), + datafeedState: datafeedStateRT, + fullJob: rt.partial({ + custom_settings: jobCustomSettingsRT, + finished_time: rt.number, + model_size_stats: jobModelSizeStatsRT, + }), + }), +]); + +export type JobSummary = rt.TypeOf; + +export const fetchJobStatusResponsePayloadRT = rt.array(jobSummaryRT); + +export type FetchJobStatusResponsePayload = rt.TypeOf; diff --git a/x-pack/plugins/infra/public/containers/ml/api/ml_get_module.ts b/x-pack/plugins/infra/public/containers/ml/api/ml_get_module.ts new file mode 100644 index 0000000000000..d492522c120a1 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/api/ml_get_module.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; +import { pipe } from 'fp-ts/lib/pipeable'; +import * as rt from 'io-ts'; +import { npStart } from '../../../legacy_singletons'; + +import { jobCustomSettingsRT } from '../../../../common/log_analysis'; +import { createPlainError, throwErrors } from '../../../../common/runtime_types'; + +export const callGetMlModuleAPI = async (moduleId: string) => { + const response = await npStart.http.fetch(`/api/ml/modules/get_module/${moduleId}`, { + method: 'GET', + }); + + return pipe( + getMlModuleResponsePayloadRT.decode(response), + fold(throwErrors(createPlainError), identity) + ); +}; + +const jobDefinitionRT = rt.type({ + id: rt.string, + config: rt.type({ + custom_settings: jobCustomSettingsRT, + }), +}); + +export type JobDefinition = rt.TypeOf; + +const getMlModuleResponsePayloadRT = rt.type({ + id: rt.string, + jobs: rt.array(jobDefinitionRT), +}); + +export type GetMlModuleResponsePayload = rt.TypeOf; diff --git a/x-pack/plugins/infra/public/containers/ml/api/ml_setup_module_api.ts b/x-pack/plugins/infra/public/containers/ml/api/ml_setup_module_api.ts new file mode 100644 index 0000000000000..06b0e075387b0 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/api/ml_setup_module_api.ts @@ -0,0 +1,115 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; +import { pipe } from 'fp-ts/lib/pipeable'; +import * as rt from 'io-ts'; +import { npStart } from '../../../legacy_singletons'; + +import { getJobIdPrefix, jobCustomSettingsRT } from '../../../../common/infra_ml'; +import { createPlainError, throwErrors } from '../../../../common/runtime_types'; + +export const callSetupMlModuleAPI = async ( + moduleId: string, + start: number | undefined, + end: number | undefined, + spaceId: string, + sourceId: string, + indexPattern: string, + jobOverrides: SetupMlModuleJobOverrides[] = [], + datafeedOverrides: SetupMlModuleDatafeedOverrides[] = [], + query?: object +) => { + const response = await npStart.http.fetch(`/api/ml/modules/setup/${moduleId}`, { + method: 'POST', + body: JSON.stringify( + setupMlModuleRequestPayloadRT.encode({ + start, + end, + indexPatternName: indexPattern, + prefix: getJobIdPrefix(spaceId, sourceId), + startDatafeed: true, + jobOverrides, + datafeedOverrides, + query, + }) + ), + }); + + return pipe( + setupMlModuleResponsePayloadRT.decode(response), + fold(throwErrors(createPlainError), identity) + ); +}; + +const setupMlModuleTimeParamsRT = rt.partial({ + start: rt.number, + end: rt.number, +}); + +const setupMlModuleJobOverridesRT = rt.type({ + job_id: rt.string, + custom_settings: jobCustomSettingsRT, +}); + +export type SetupMlModuleJobOverrides = rt.TypeOf; + +const setupMlModuleDatafeedOverridesRT = rt.object; + +export type SetupMlModuleDatafeedOverrides = rt.TypeOf; + +const setupMlModuleRequestParamsRT = rt.intersection([ + rt.strict({ + indexPatternName: rt.string, + prefix: rt.string, + startDatafeed: rt.boolean, + jobOverrides: rt.array(setupMlModuleJobOverridesRT), + datafeedOverrides: rt.array(setupMlModuleDatafeedOverridesRT), + }), + rt.exact( + rt.partial({ + query: rt.object, + }) + ), +]); + +const setupMlModuleRequestPayloadRT = rt.intersection([ + setupMlModuleTimeParamsRT, + setupMlModuleRequestParamsRT, +]); + +const setupErrorResponseRT = rt.type({ + msg: rt.string, +}); + +const datafeedSetupResponseRT = rt.intersection([ + rt.type({ + id: rt.string, + started: rt.boolean, + success: rt.boolean, + }), + rt.partial({ + error: setupErrorResponseRT, + }), +]); + +const jobSetupResponseRT = rt.intersection([ + rt.type({ + id: rt.string, + success: rt.boolean, + }), + rt.partial({ + error: setupErrorResponseRT, + }), +]); + +const setupMlModuleResponsePayloadRT = rt.type({ + datafeeds: rt.array(datafeedSetupResponseRT), + jobs: rt.array(jobSetupResponseRT), +}); + +export type SetupMlModuleResponsePayload = rt.TypeOf; diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_capabilities.tsx b/x-pack/plugins/infra/public/containers/ml/infra_ml_capabilities.tsx new file mode 100644 index 0000000000000..f4c90a459af6a --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_capabilities.tsx @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import createContainer from 'constate'; +import { useMemo, useState, useEffect } from 'react'; +import { fold } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { identity } from 'fp-ts/lib/function'; +import { useTrackedPromise } from '../../utils/use_tracked_promise'; +import { npStart } from '../../legacy_singletons'; +import { + getMlCapabilitiesResponsePayloadRT, + GetMlCapabilitiesResponsePayload, +} from './api/ml_api_types'; +import { throwErrors, createPlainError } from '../../../common/runtime_types'; + +export const useInfraMLCapabilities = () => { + const [mlCapabilities, setMlCapabilities] = useState( + initialMlCapabilities + ); + + const [fetchMlCapabilitiesRequest, fetchMlCapabilities] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async () => { + const rawResponse = await npStart.http.fetch('/api/ml/ml_capabilities'); + + return pipe( + getMlCapabilitiesResponsePayloadRT.decode(rawResponse), + fold(throwErrors(createPlainError), identity) + ); + }, + onResolve: (response) => { + setMlCapabilities(response); + }, + }, + [] + ); + + useEffect(() => { + fetchMlCapabilities(); + }, [fetchMlCapabilities]); + + const isLoading = useMemo(() => fetchMlCapabilitiesRequest.state === 'pending', [ + fetchMlCapabilitiesRequest.state, + ]); + + const hasInfraMLSetupCapabilities = mlCapabilities.capabilities.canCreateJob; + const hasInfraMLReadCapabilities = mlCapabilities.capabilities.canGetJobs; + const hasInfraMLCapabilites = + mlCapabilities.isPlatinumOrTrialLicense && mlCapabilities.mlFeatureEnabledInSpace; + + return { + hasInfraMLCapabilites, + hasInfraMLReadCapabilities, + hasInfraMLSetupCapabilities, + isLoading, + }; +}; + +export const [InfraMLCapabilitiesProvider, useInfraMLCapabilitiesContext] = createContainer( + useInfraMLCapabilities +); + +const initialMlCapabilities = { + capabilities: { + canGetJobs: false, + canCreateJob: false, + canDeleteJob: false, + canOpenJob: false, + canCloseJob: false, + canForecastJob: false, + canGetDatafeeds: false, + canStartStopDatafeed: false, + canUpdateJob: false, + canUpdateDatafeed: false, + canPreviewDatafeed: false, + canGetCalendars: false, + canCreateCalendar: false, + canDeleteCalendar: false, + canGetFilters: false, + canCreateFilter: false, + canDeleteFilter: false, + canFindFileStructure: false, + canGetDataFrameJobs: false, + canDeleteDataFrameJob: false, + canPreviewDataFrameJob: false, + canCreateDataFrameJob: false, + canStartStopDataFrameJob: false, + }, + isPlatinumOrTrialLicense: false, + mlFeatureEnabledInSpace: false, + upgradeInProgress: false, +}; diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_cleanup.tsx b/x-pack/plugins/infra/public/containers/ml/infra_ml_cleanup.tsx new file mode 100644 index 0000000000000..736982c8043b1 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_cleanup.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getJobId } from '../../../common/infra_ml'; +import { callDeleteJobs, callGetJobDeletionTasks, callStopDatafeeds } from './api/ml_cleanup'; + +export const cleanUpJobsAndDatafeeds = async ( + spaceId: string, + sourceId: string, + jobTypes: JobType[] +) => { + try { + await callStopDatafeeds(spaceId, sourceId, jobTypes); + } catch (err) { + // Proceed only if datafeed has been deleted or didn't exist in the first place + if (err?.res?.status !== 404) { + throw err; + } + } + + return await deleteJobs(spaceId, sourceId, jobTypes); +}; + +const deleteJobs = async ( + spaceId: string, + sourceId: string, + jobTypes: JobType[] +) => { + const deleteJobsResponse = await callDeleteJobs(spaceId, sourceId, jobTypes); + await waitUntilJobsAreDeleted(spaceId, sourceId, jobTypes); + return deleteJobsResponse; +}; + +const waitUntilJobsAreDeleted = async ( + spaceId: string, + sourceId: string, + jobTypes: JobType[] +) => { + const moduleJobIds = jobTypes.map((jobType) => getJobId(spaceId, sourceId, jobType)); + while (true) { + const { jobIds: jobIdsBeingDeleted } = await callGetJobDeletionTasks(); + const needToWait = jobIdsBeingDeleted.some((jobId) => moduleJobIds.includes(jobId)); + + if (needToWait) { + await timeout(1000); + } else { + return true; + } + } +}; + +const timeout = (ms: number) => new Promise((res) => setTimeout(res, ms)); diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_module.tsx b/x-pack/plugins/infra/public/containers/ml/infra_ml_module.tsx new file mode 100644 index 0000000000000..349541d108f5e --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_module.tsx @@ -0,0 +1,147 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useCallback, useMemo } from 'react'; +import { DatasetFilter } from '../../../common/infra_ml'; +import { useTrackedPromise } from '../../utils/use_tracked_promise'; +import { useModuleStatus } from './infra_ml_module_status'; +import { ModuleDescriptor, ModuleSourceConfiguration } from './infra_ml_module_types'; + +export const useInfraMLModule = ({ + sourceConfiguration, + moduleDescriptor, +}: { + sourceConfiguration: ModuleSourceConfiguration; + moduleDescriptor: ModuleDescriptor; +}) => { + const { spaceId, sourceId, timestampField } = sourceConfiguration; + const [moduleStatus, dispatchModuleStatus] = useModuleStatus(moduleDescriptor.jobTypes); + + const [, fetchJobStatus] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async () => { + dispatchModuleStatus({ type: 'fetchingJobStatuses' }); + return await moduleDescriptor.getJobSummary(spaceId, sourceId); + }, + onResolve: (jobResponse) => { + dispatchModuleStatus({ + type: 'fetchedJobStatuses', + payload: jobResponse, + spaceId, + sourceId, + }); + }, + onReject: () => { + dispatchModuleStatus({ type: 'failedFetchingJobStatuses' }); + }, + }, + [spaceId, sourceId] + ); + + const [, setUpModule] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async ( + selectedIndices: string[], + start: number | undefined, + end: number | undefined, + datasetFilter: DatasetFilter, + partitionField?: string + ) => { + dispatchModuleStatus({ type: 'startedSetup' }); + const setupResult = await moduleDescriptor.setUpModule( + start, + end, + datasetFilter, + { + indices: selectedIndices, + sourceId, + spaceId, + timestampField, + }, + partitionField + ); + const jobSummaries = await moduleDescriptor.getJobSummary(spaceId, sourceId); + return { setupResult, jobSummaries }; + }, + onResolve: ({ setupResult: { datafeeds, jobs }, jobSummaries }) => { + dispatchModuleStatus({ + type: 'finishedSetup', + datafeedSetupResults: datafeeds, + jobSetupResults: jobs, + jobSummaries, + spaceId, + sourceId, + }); + }, + onReject: () => { + dispatchModuleStatus({ type: 'failedSetup' }); + }, + }, + [moduleDescriptor.setUpModule, spaceId, sourceId, timestampField] + ); + + const [cleanUpModuleRequest, cleanUpModule] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async () => { + return await moduleDescriptor.cleanUpModule(spaceId, sourceId); + }, + }, + [spaceId, sourceId] + ); + + const isCleaningUp = useMemo(() => cleanUpModuleRequest.state === 'pending', [ + cleanUpModuleRequest.state, + ]); + + const cleanUpAndSetUpModule = useCallback( + ( + selectedIndices: string[], + start: number | undefined, + end: number | undefined, + datasetFilter: DatasetFilter, + partitionField?: string + ) => { + dispatchModuleStatus({ type: 'startedSetup' }); + cleanUpModule() + .then(() => { + setUpModule(selectedIndices, start, end, datasetFilter, partitionField); + }) + .catch(() => { + dispatchModuleStatus({ type: 'failedSetup' }); + }); + }, + [cleanUpModule, dispatchModuleStatus, setUpModule] + ); + + const viewResults = useCallback(() => { + dispatchModuleStatus({ type: 'viewedResults' }); + }, [dispatchModuleStatus]); + + const jobIds = useMemo(() => moduleDescriptor.getJobIds(spaceId, sourceId), [ + moduleDescriptor, + spaceId, + sourceId, + ]); + + return { + cleanUpAndSetUpModule, + cleanUpModule, + fetchJobStatus, + isCleaningUp, + jobIds, + jobStatus: moduleStatus.jobStatus, + jobSummaries: moduleStatus.jobSummaries, + lastSetupErrorMessages: moduleStatus.lastSetupErrorMessages, + moduleDescriptor, + setUpModule, + setupStatus: moduleStatus.setupStatus, + sourceConfiguration, + viewResults, + }; +}; diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_module_configuration.ts b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_configuration.ts new file mode 100644 index 0000000000000..2d90f5d531010 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_configuration.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useMemo } from 'react'; +import { JobSummary } from './api/ml_get_jobs_summary_api'; +import { ModuleDescriptor, ModuleSourceConfiguration } from './infra_ml_module_types'; + +export const useInfraMLModuleConfiguration = ({ + moduleDescriptor, + sourceConfiguration, +}: { + moduleDescriptor: ModuleDescriptor; + sourceConfiguration: ModuleSourceConfiguration; +}) => { + const getIsJobConfigurationOutdated = useMemo( + () => isJobConfigurationOutdated(moduleDescriptor, sourceConfiguration), + [sourceConfiguration, moduleDescriptor] + ); + + return { + getIsJobConfigurationOutdated, + }; +}; + +export const isJobConfigurationOutdated = ( + { bucketSpan }: ModuleDescriptor, + currentSourceConfiguration: ModuleSourceConfiguration +) => (jobSummary: JobSummary): boolean => { + if (!jobSummary.fullJob || !jobSummary.fullJob.custom_settings) { + return false; + } + + const jobConfiguration = jobSummary.fullJob.custom_settings.metrics_source_config; + + return !( + jobConfiguration && + jobConfiguration.bucketSpan === bucketSpan && + jobConfiguration.indexPattern && + isSubset( + new Set(jobConfiguration.indexPattern.split(',')), + new Set(currentSourceConfiguration.indices) + ) && + jobConfiguration.timestampField === currentSourceConfiguration.timestampField + ); +}; + +const isSubset = (subset: Set, superset: Set) => { + return Array.from(subset).every((subsetElement) => superset.has(subsetElement)); +}; diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_module_definition.tsx b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_definition.tsx new file mode 100644 index 0000000000000..3c7ffcfd4a4e2 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_definition.tsx @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useCallback, useMemo, useState } from 'react'; +import { getJobId } from '../../../common/log_analysis'; +import { useTrackedPromise } from '../../utils/use_tracked_promise'; +import { JobSummary } from './api/ml_get_jobs_summary_api'; +import { GetMlModuleResponsePayload, JobDefinition } from './api/ml_get_module'; +import { ModuleDescriptor, ModuleSourceConfiguration } from './infra_ml_module_types'; + +export const useInfraMLModuleDefinition = ({ + sourceConfiguration: { spaceId, sourceId }, + moduleDescriptor, +}: { + sourceConfiguration: ModuleSourceConfiguration; + moduleDescriptor: ModuleDescriptor; +}) => { + const [moduleDefinition, setModuleDefinition] = useState< + GetMlModuleResponsePayload | undefined + >(); + + const jobDefinitionByJobId = useMemo( + () => + moduleDefinition + ? moduleDefinition.jobs.reduce>( + (accumulatedJobDefinitions, jobDefinition) => ({ + ...accumulatedJobDefinitions, + [getJobId(spaceId, sourceId, jobDefinition.id)]: jobDefinition, + }), + {} + ) + : {}, + [moduleDefinition, sourceId, spaceId] + ); + + const [fetchModuleDefinitionRequest, fetchModuleDefinition] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async () => { + return await moduleDescriptor.getModuleDefinition(); + }, + onResolve: (response) => { + setModuleDefinition(response); + }, + onReject: () => { + setModuleDefinition(undefined); + }, + }, + [moduleDescriptor.getModuleDefinition, spaceId, sourceId] + ); + + const getIsJobDefinitionOutdated = useCallback( + (jobSummary: JobSummary): boolean => { + const jobDefinition: JobDefinition | undefined = jobDefinitionByJobId[jobSummary.id]; + + if (jobDefinition == null) { + return false; + } + + const currentRevision = jobDefinition?.config.custom_settings.job_revision; + return (jobSummary.fullJob?.custom_settings?.job_revision ?? 0) < (currentRevision ?? 0); + }, + [jobDefinitionByJobId] + ); + + return { + fetchModuleDefinition, + fetchModuleDefinitionRequestState: fetchModuleDefinitionRequest.state, + getIsJobDefinitionOutdated, + jobDefinitionByJobId, + moduleDefinition, + }; +}; diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_module_status.tsx b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_status.tsx new file mode 100644 index 0000000000000..63d479546b44f --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_status.tsx @@ -0,0 +1,268 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useReducer } from 'react'; + +import { + JobStatus, + getDatafeedId, + getJobId, + isJobStatusWithResults, + SetupStatus, +} from '../../../common/infra_ml'; +import { FetchJobStatusResponsePayload, JobSummary } from './api/ml_get_jobs_summary_api'; +import { SetupMlModuleResponsePayload } from './api/ml_setup_module_api'; +import { MandatoryProperty } from '../../../common/utility_types'; + +interface StatusReducerState { + jobStatus: Record; + jobSummaries: JobSummary[]; + lastSetupErrorMessages: string[]; + setupStatus: SetupStatus; +} + +type StatusReducerAction = + | { type: 'startedSetup' } + | { + type: 'finishedSetup'; + sourceId: string; + spaceId: string; + jobSetupResults: SetupMlModuleResponsePayload['jobs']; + jobSummaries: FetchJobStatusResponsePayload; + datafeedSetupResults: SetupMlModuleResponsePayload['datafeeds']; + } + | { type: 'failedSetup' } + | { type: 'fetchingJobStatuses' } + | { + type: 'fetchedJobStatuses'; + spaceId: string; + sourceId: string; + payload: FetchJobStatusResponsePayload; + } + | { type: 'failedFetchingJobStatuses' } + | { type: 'viewedResults' }; + +const createInitialState = ({ + jobTypes, +}: { + jobTypes: JobType[]; +}): StatusReducerState => ({ + jobStatus: jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: 'unknown', + }), + {} as Record + ), + jobSummaries: [], + lastSetupErrorMessages: [], + setupStatus: { type: 'initializing' }, +}); + +const createStatusReducer = (jobTypes: JobType[]) => ( + state: StatusReducerState, + action: StatusReducerAction +): StatusReducerState => { + switch (action.type) { + case 'startedSetup': { + return { + ...state, + jobStatus: jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: 'initializing', + }), + {} as Record + ), + setupStatus: { type: 'pending' }, + }; + } + case 'finishedSetup': { + const { datafeedSetupResults, jobSetupResults, jobSummaries, spaceId, sourceId } = action; + const nextJobStatus = jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: + hasSuccessfullyCreatedJob(getJobId(spaceId, sourceId, jobType))(jobSetupResults) && + hasSuccessfullyStartedDatafeed(getDatafeedId(spaceId, sourceId, jobType))( + datafeedSetupResults + ) + ? 'started' + : 'failed', + }), + {} as Record + ); + const nextSetupStatus: SetupStatus = Object.values(nextJobStatus).every( + (jobState) => jobState === 'started' + ) + ? { type: 'succeeded' } + : { + type: 'failed', + reasons: [ + ...Object.values(datafeedSetupResults) + .filter(hasError) + .map((datafeed) => datafeed.error.msg), + ...Object.values(jobSetupResults) + .filter(hasError) + .map((job) => job.error.msg), + ], + }; + + return { + ...state, + jobStatus: nextJobStatus, + jobSummaries, + setupStatus: nextSetupStatus, + }; + } + case 'failedSetup': { + return { + ...state, + jobStatus: jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: 'failed', + }), + {} as Record + ), + setupStatus: { type: 'failed', reasons: ['unknown'] }, + }; + } + case 'fetchingJobStatuses': { + return { + ...state, + setupStatus: + state.setupStatus.type === 'unknown' ? { type: 'initializing' } : state.setupStatus, + }; + } + case 'fetchedJobStatuses': { + const { payload: jobSummaries, spaceId, sourceId } = action; + const { setupStatus } = state; + const nextJobStatus = jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: getJobStatus(getJobId(spaceId, sourceId, jobType))(jobSummaries), + }), + {} as Record + ); + const nextSetupStatus = getSetupStatus(nextJobStatus)(setupStatus); + + return { + ...state, + jobSummaries, + jobStatus: nextJobStatus, + setupStatus: nextSetupStatus, + }; + } + case 'failedFetchingJobStatuses': { + return { + ...state, + setupStatus: { type: 'unknown' }, + jobStatus: jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: 'unknown', + }), + {} as Record + ), + }; + } + case 'viewedResults': { + return { + ...state, + setupStatus: { type: 'skipped', newlyCreated: true }, + }; + } + default: { + return state; + } + } +}; + +const hasSuccessfullyCreatedJob = (jobId: string) => ( + jobSetupResponses: SetupMlModuleResponsePayload['jobs'] +) => + jobSetupResponses.filter( + (jobSetupResponse) => + jobSetupResponse.id === jobId && jobSetupResponse.success && !jobSetupResponse.error + ).length > 0; + +const hasSuccessfullyStartedDatafeed = (datafeedId: string) => ( + datafeedSetupResponses: SetupMlModuleResponsePayload['datafeeds'] +) => + datafeedSetupResponses.filter( + (datafeedSetupResponse) => + datafeedSetupResponse.id === datafeedId && + datafeedSetupResponse.success && + datafeedSetupResponse.started && + !datafeedSetupResponse.error + ).length > 0; + +const getJobStatus = (jobId: string) => ( + jobSummaries: FetchJobStatusResponsePayload +): JobStatus => { + return ( + jobSummaries + .filter((jobSummary) => jobSummary.id === jobId) + .map( + (jobSummary): JobStatus => { + if (jobSummary.jobState === 'failed' || jobSummary.datafeedState === '') { + return 'failed'; + } else if ( + jobSummary.jobState === 'closed' && + jobSummary.datafeedState === 'stopped' && + jobSummary.fullJob && + jobSummary.fullJob.finished_time != null + ) { + return 'finished'; + } else if ( + jobSummary.jobState === 'closed' || + jobSummary.jobState === 'closing' || + jobSummary.datafeedState === 'stopped' + ) { + return 'stopped'; + } else if (jobSummary.jobState === 'opening') { + return 'initializing'; + } else if (jobSummary.jobState === 'opened' && jobSummary.datafeedState === 'started') { + return 'started'; + } + + return 'unknown'; + } + )[0] || 'missing' + ); +}; + +const getSetupStatus = (everyJobStatus: Record) => ( + previousSetupStatus: SetupStatus +): SetupStatus => { + return Object.entries(everyJobStatus).reduce( + (setupStatus, [, jobStatus]) => { + if (jobStatus === 'missing') { + return { type: 'required' }; + } else if (setupStatus.type === 'required' || setupStatus.type === 'succeeded') { + return setupStatus; + } else if (setupStatus.type === 'skipped' || isJobStatusWithResults(jobStatus)) { + return { + type: 'skipped', + // preserve newlyCreated status + newlyCreated: setupStatus.type === 'skipped' && setupStatus.newlyCreated, + }; + } + + return setupStatus; + }, + previousSetupStatus + ); +}; + +const hasError = ( + value: Value +): value is MandatoryProperty => value.error != null; + +export const useModuleStatus = (jobTypes: JobType[]) => { + return useReducer(createStatusReducer(jobTypes), { jobTypes }, createInitialState); +}; diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_module_types.ts b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_types.ts new file mode 100644 index 0000000000000..a9f2671de8259 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_types.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + ValidateLogEntryDatasetsResponsePayload, + ValidationIndicesResponsePayload, +} from '../../../common/http_api/log_analysis'; +import { DatasetFilter } from '../../../common/infra_ml'; +import { DeleteJobsResponsePayload } from './api/ml_cleanup'; +import { FetchJobStatusResponsePayload } from './api/ml_get_jobs_summary_api'; +import { GetMlModuleResponsePayload } from './api/ml_get_module'; +import { SetupMlModuleResponsePayload } from './api/ml_setup_module_api'; + +export { JobModelSizeStats, JobSummary } from './api/ml_get_jobs_summary_api'; + +export interface ModuleDescriptor { + moduleId: string; + moduleName: string; + moduleDescription: string; + jobTypes: JobType[]; + bucketSpan: number; + getJobIds: (spaceId: string, sourceId: string) => Record; + getJobSummary: (spaceId: string, sourceId: string) => Promise; + getModuleDefinition: () => Promise; + setUpModule: ( + start: number | undefined, + end: number | undefined, + datasetFilter: DatasetFilter, + sourceConfiguration: ModuleSourceConfiguration, + partitionField?: string + ) => Promise; + cleanUpModule: (spaceId: string, sourceId: string) => Promise; + validateSetupIndices: ( + indices: string[], + timestampField: string + ) => Promise; + validateSetupDatasets: ( + indices: string[], + timestampField: string, + startTime: number, + endTime: number + ) => Promise; +} + +export interface ModuleSourceConfiguration { + indices: string[]; + sourceId: string; + spaceId: string; + timestampField: string; +} + +interface ManyCategoriesWarningReason { + type: 'manyCategories'; + categoriesDocumentRatio: number; +} + +interface ManyDeadCategoriesWarningReason { + type: 'manyDeadCategories'; + deadCategoriesRatio: number; +} + +interface ManyRareCategoriesWarningReason { + type: 'manyRareCategories'; + rareCategoriesRatio: number; +} + +interface NoFrequentCategoriesWarningReason { + type: 'noFrequentCategories'; +} + +interface SingleCategoryWarningReason { + type: 'singleCategory'; +} + +export type CategoryQualityWarningReason = + | ManyCategoriesWarningReason + | ManyDeadCategoriesWarningReason + | ManyRareCategoriesWarningReason + | NoFrequentCategoriesWarningReason + | SingleCategoryWarningReason; + +export type CategoryQualityWarningReasonType = CategoryQualityWarningReason['type']; + +export interface CategoryQualityWarning { + type: 'categoryQualityWarning'; + jobId: string; + reasons: CategoryQualityWarningReason[]; +} + +export type QualityWarning = CategoryQualityWarning; diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_setup_state.ts b/x-pack/plugins/infra/public/containers/ml/infra_ml_setup_state.ts new file mode 100644 index 0000000000000..0dfe3b301f240 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_setup_state.ts @@ -0,0 +1,289 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { isEqual } from 'lodash'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { usePrevious } from 'react-use'; +import { + combineDatasetFilters, + DatasetFilter, + filterDatasetFilter, + isExampleDataIndex, +} from '../../../common/infra_ml'; +import { + AvailableIndex, + ValidationIndicesError, + ValidationUIError, +} from '../../components/logging/log_analysis_setup/initial_configuration_step'; +import { useTrackedPromise } from '../../utils/use_tracked_promise'; +import { ModuleDescriptor, ModuleSourceConfiguration } from './infra_ml_module_types'; + +type SetupHandler = ( + indices: string[], + startTime: number | undefined, + endTime: number | undefined, + datasetFilter: DatasetFilter +) => void; + +interface AnalysisSetupStateArguments { + cleanUpAndSetUpModule: SetupHandler; + moduleDescriptor: ModuleDescriptor; + setUpModule: SetupHandler; + sourceConfiguration: ModuleSourceConfiguration; +} + +const fourWeeksInMs = 86400000 * 7 * 4; + +export const useAnalysisSetupState = ({ + cleanUpAndSetUpModule, + moduleDescriptor: { validateSetupDatasets, validateSetupIndices }, + setUpModule, + sourceConfiguration, +}: AnalysisSetupStateArguments) => { + const [startTime, setStartTime] = useState(Date.now() - fourWeeksInMs); + const [endTime, setEndTime] = useState(undefined); + + const isTimeRangeValid = useMemo( + () => (startTime != null && endTime != null ? startTime < endTime : true), + [endTime, startTime] + ); + + const [validatedIndices, setValidatedIndices] = useState( + sourceConfiguration.indices.map((indexName) => ({ + name: indexName, + validity: 'unknown' as const, + })) + ); + + const updateIndicesWithValidationErrors = useCallback( + (validationErrors: ValidationIndicesError[]) => + setValidatedIndices((availableIndices) => + availableIndices.map((previousAvailableIndex) => { + const indexValiationErrors = validationErrors.filter( + ({ index }) => index === previousAvailableIndex.name + ); + + if (indexValiationErrors.length > 0) { + return { + validity: 'invalid', + name: previousAvailableIndex.name, + errors: indexValiationErrors, + }; + } else if (previousAvailableIndex.validity === 'valid') { + return { + ...previousAvailableIndex, + validity: 'valid', + errors: [], + }; + } else { + return { + validity: 'valid', + name: previousAvailableIndex.name, + isSelected: !isExampleDataIndex(previousAvailableIndex.name), + availableDatasets: [], + datasetFilter: { + type: 'includeAll' as const, + }, + }; + } + }) + ), + [] + ); + + const updateIndicesWithAvailableDatasets = useCallback( + (availableDatasets: Array<{ indexName: string; datasets: string[] }>) => + setValidatedIndices((availableIndices) => + availableIndices.map((previousAvailableIndex) => { + if (previousAvailableIndex.validity !== 'valid') { + return previousAvailableIndex; + } + + const availableDatasetsForIndex = availableDatasets.filter( + ({ indexName }) => indexName === previousAvailableIndex.name + ); + const newAvailableDatasets = availableDatasetsForIndex.flatMap( + ({ datasets }) => datasets + ); + + // filter out datasets that have disappeared if this index' datasets were updated + const newDatasetFilter: DatasetFilter = + availableDatasetsForIndex.length > 0 + ? filterDatasetFilter(previousAvailableIndex.datasetFilter, (dataset) => + newAvailableDatasets.includes(dataset) + ) + : previousAvailableIndex.datasetFilter; + + return { + ...previousAvailableIndex, + availableDatasets: newAvailableDatasets, + datasetFilter: newDatasetFilter, + }; + }) + ), + [] + ); + + const validIndexNames = useMemo( + () => validatedIndices.filter((index) => index.validity === 'valid').map((index) => index.name), + [validatedIndices] + ); + + const selectedIndexNames = useMemo( + () => + validatedIndices + .filter((index) => index.validity === 'valid' && index.isSelected) + .map((i) => i.name), + [validatedIndices] + ); + + const datasetFilter = useMemo( + () => + validatedIndices + .flatMap((validatedIndex) => + validatedIndex.validity === 'valid' + ? validatedIndex.datasetFilter + : { type: 'includeAll' as const } + ) + .reduce(combineDatasetFilters, { type: 'includeAll' as const }), + [validatedIndices] + ); + + const [validateIndicesRequest, validateIndices] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async () => { + return await validateSetupIndices( + sourceConfiguration.indices, + sourceConfiguration.timestampField + ); + }, + onResolve: ({ data: { errors } }) => { + updateIndicesWithValidationErrors(errors); + }, + onReject: () => { + setValidatedIndices([]); + }, + }, + [sourceConfiguration.indices, sourceConfiguration.timestampField] + ); + + const [validateDatasetsRequest, validateDatasets] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async () => { + if (validIndexNames.length === 0) { + return { data: { datasets: [] } }; + } + + return await validateSetupDatasets( + validIndexNames, + sourceConfiguration.timestampField, + startTime ?? 0, + endTime ?? Date.now() + ); + }, + onResolve: ({ data: { datasets } }) => { + updateIndicesWithAvailableDatasets(datasets); + }, + }, + [validIndexNames, sourceConfiguration.timestampField, startTime, endTime] + ); + + const setUp = useCallback(() => { + return setUpModule(selectedIndexNames, startTime, endTime, datasetFilter); + }, [setUpModule, selectedIndexNames, startTime, endTime, datasetFilter]); + + const cleanUpAndSetUp = useCallback(() => { + return cleanUpAndSetUpModule(selectedIndexNames, startTime, endTime, datasetFilter); + }, [cleanUpAndSetUpModule, selectedIndexNames, startTime, endTime, datasetFilter]); + + const isValidating = useMemo( + () => validateIndicesRequest.state === 'pending' || validateDatasetsRequest.state === 'pending', + [validateDatasetsRequest.state, validateIndicesRequest.state] + ); + + const validationErrors = useMemo(() => { + if (isValidating) { + return []; + } + + return [ + // validate request status + ...(validateIndicesRequest.state === 'rejected' || + validateDatasetsRequest.state === 'rejected' + ? [{ error: 'NETWORK_ERROR' as const }] + : []), + // validation request results + ...validatedIndices.reduce((errors, index) => { + return index.validity === 'invalid' && selectedIndexNames.includes(index.name) + ? [...errors, ...index.errors] + : errors; + }, []), + // index count + ...(selectedIndexNames.length === 0 ? [{ error: 'TOO_FEW_SELECTED_INDICES' as const }] : []), + // time range + ...(!isTimeRangeValid ? [{ error: 'INVALID_TIME_RANGE' as const }] : []), + ]; + }, [ + isValidating, + validateIndicesRequest.state, + validateDatasetsRequest.state, + validatedIndices, + selectedIndexNames, + isTimeRangeValid, + ]); + + const prevStartTime = usePrevious(startTime); + const prevEndTime = usePrevious(endTime); + const prevValidIndexNames = usePrevious(validIndexNames); + + useEffect(() => { + if (!isTimeRangeValid) { + return; + } + + validateIndices(); + }, [isTimeRangeValid, validateIndices]); + + useEffect(() => { + if (!isTimeRangeValid) { + return; + } + + if ( + startTime !== prevStartTime || + endTime !== prevEndTime || + !isEqual(validIndexNames, prevValidIndexNames) + ) { + validateDatasets(); + } + }, [ + endTime, + isTimeRangeValid, + prevEndTime, + prevStartTime, + prevValidIndexNames, + startTime, + validIndexNames, + validateDatasets, + ]); + + return { + cleanUpAndSetUp, + datasetFilter, + endTime, + isValidating, + selectedIndexNames, + setEndTime, + setStartTime, + setUp, + startTime, + validatedIndices, + setValidatedIndices, + validationErrors, + }; +}; diff --git a/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module.tsx b/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module.tsx new file mode 100644 index 0000000000000..9c065f3e91bc4 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module.tsx @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import createContainer from 'constate'; +import { useMemo } from 'react'; +import { useInfraMLModule } from '../../infra_ml_module'; +import { useInfraMLModuleConfiguration } from '../../infra_ml_module_configuration'; +import { useInfraMLModuleDefinition } from '../../infra_ml_module_definition'; +import { ModuleSourceConfiguration } from '../../infra_ml_module_types'; +import { metricHostsModule } from './module_descriptor'; + +export const useMetricHostsModule = ({ + indexPattern, + sourceId, + spaceId, + timestampField, +}: { + indexPattern: string; + sourceId: string; + spaceId: string; + timestampField: string; +}) => { + const sourceConfiguration: ModuleSourceConfiguration = useMemo( + () => ({ + indices: indexPattern.split(','), + sourceId, + spaceId, + timestampField, + }), + [indexPattern, sourceId, spaceId, timestampField] + ); + + const infraMLModule = useInfraMLModule({ + moduleDescriptor: metricHostsModule, + sourceConfiguration, + }); + + const { getIsJobConfigurationOutdated } = useInfraMLModuleConfiguration({ + sourceConfiguration, + moduleDescriptor: metricHostsModule, + }); + + const { fetchModuleDefinition, getIsJobDefinitionOutdated } = useInfraMLModuleDefinition({ + sourceConfiguration, + moduleDescriptor: metricHostsModule, + }); + + const hasOutdatedJobConfigurations = useMemo( + () => infraMLModule.jobSummaries.some(getIsJobConfigurationOutdated), + [getIsJobConfigurationOutdated, infraMLModule.jobSummaries] + ); + + const hasOutdatedJobDefinitions = useMemo( + () => infraMLModule.jobSummaries.some(getIsJobDefinitionOutdated), + [getIsJobDefinitionOutdated, infraMLModule.jobSummaries] + ); + + const hasStoppedJobs = useMemo( + () => + Object.values(infraMLModule.jobStatus).some( + (currentJobStatus) => currentJobStatus === 'stopped' + ), + [infraMLModule.jobStatus] + ); + + return { + ...infraMLModule, + fetchModuleDefinition, + hasOutdatedJobConfigurations, + hasOutdatedJobDefinitions, + hasStoppedJobs, + }; +}; + +export const [MetricHostsModuleProvider, useMetricHostsModuleContext] = createContainer( + useMetricHostsModule +); diff --git a/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module_descriptor.ts b/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module_descriptor.ts new file mode 100644 index 0000000000000..cec87fb1144e3 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module_descriptor.ts @@ -0,0 +1,126 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { ModuleDescriptor, ModuleSourceConfiguration } from '../../infra_ml_module_types'; +import { cleanUpJobsAndDatafeeds } from '../../infra_ml_cleanup'; +import { callJobsSummaryAPI } from '../../api/ml_get_jobs_summary_api'; +import { callGetMlModuleAPI } from '../../api/ml_get_module'; +import { callSetupMlModuleAPI } from '../../api/ml_setup_module_api'; +import { callValidateIndicesAPI } from '../../../logs/log_analysis/api/validate_indices'; +import { callValidateDatasetsAPI } from '../../../logs/log_analysis/api/validate_datasets'; +import { + metricsHostsJobTypes, + getJobId, + MetricsHostsJobType, + DatasetFilter, + bucketSpan, + partitionField, +} from '../../../../../common/infra_ml'; + +const moduleId = 'metrics_ui_hosts'; +const moduleName = i18n.translate('xpack.infra.ml.metricsModuleName', { + defaultMessage: 'Metrics anomanly detection', +}); +const moduleDescription = i18n.translate('xpack.infra.ml.metricsHostModuleDescription', { + defaultMessage: 'Use Machine Learning to automatically detect anomalous log entry rates.', +}); + +const getJobIds = (spaceId: string, sourceId: string) => + metricsHostsJobTypes.reduce( + (accumulatedJobIds, jobType) => ({ + ...accumulatedJobIds, + [jobType]: getJobId(spaceId, sourceId, jobType), + }), + {} as Record + ); + +const getJobSummary = async (spaceId: string, sourceId: string) => { + const response = await callJobsSummaryAPI(spaceId, sourceId, metricsHostsJobTypes); + const jobIds = Object.values(getJobIds(spaceId, sourceId)); + + return response.filter((jobSummary) => jobIds.includes(jobSummary.id)); +}; + +const getModuleDefinition = async () => { + return await callGetMlModuleAPI(moduleId); +}; + +const setUpModule = async ( + start: number | undefined, + end: number | undefined, + datasetFilter: DatasetFilter, + { spaceId, sourceId, indices, timestampField }: ModuleSourceConfiguration, + pField?: string +) => { + const indexNamePattern = indices.join(','); + const jobIds = ['hosts_memory_usage', 'hosts_network_in', 'hosts_network_out']; + const jobOverrides = jobIds.map((id) => ({ + job_id: id, + data_description: { + time_field: timestampField, + }, + custom_settings: { + metrics_source_config: { + indexPattern: indexNamePattern, + timestampField, + bucketSpan, + }, + }, + })); + + return callSetupMlModuleAPI( + moduleId, + start, + end, + spaceId, + sourceId, + indexNamePattern, + jobOverrides, + [] + ); +}; + +const cleanUpModule = async (spaceId: string, sourceId: string) => { + return await cleanUpJobsAndDatafeeds(spaceId, sourceId, metricsHostsJobTypes); +}; + +const validateSetupIndices = async (indices: string[], timestampField: string) => { + return await callValidateIndicesAPI(indices, [ + { + name: timestampField, + validTypes: ['date'], + }, + { + name: partitionField, + validTypes: ['keyword'], + }, + ]); +}; + +const validateSetupDatasets = async ( + indices: string[], + timestampField: string, + startTime: number, + endTime: number +) => { + return await callValidateDatasetsAPI(indices, timestampField, startTime, endTime); +}; + +export const metricHostsModule: ModuleDescriptor = { + moduleId, + moduleName, + moduleDescription, + jobTypes: metricsHostsJobTypes, + bucketSpan, + getJobIds, + getJobSummary, + getModuleDefinition, + setUpModule, + cleanUpModule, + validateSetupDatasets, + validateSetupIndices, +}; diff --git a/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module.tsx b/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module.tsx new file mode 100644 index 0000000000000..07c8ab02f17ee --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module.tsx @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import createContainer from 'constate'; +import { useMemo } from 'react'; +import { useInfraMLModule } from '../../infra_ml_module'; +import { useInfraMLModuleConfiguration } from '../../infra_ml_module_configuration'; +import { useInfraMLModuleDefinition } from '../../infra_ml_module_definition'; +import { ModuleSourceConfiguration } from '../../infra_ml_module_types'; +import { metricHostsModule } from './module_descriptor'; + +export const useMetricK8sModule = ({ + indexPattern, + sourceId, + spaceId, + timestampField, +}: { + indexPattern: string; + sourceId: string; + spaceId: string; + timestampField: string; +}) => { + const sourceConfiguration: ModuleSourceConfiguration = useMemo( + () => ({ + indices: indexPattern.split(','), + sourceId, + spaceId, + timestampField, + }), + [indexPattern, sourceId, spaceId, timestampField] + ); + + const infraMLModule = useInfraMLModule({ + moduleDescriptor: metricHostsModule, + sourceConfiguration, + }); + + const { getIsJobConfigurationOutdated } = useInfraMLModuleConfiguration({ + sourceConfiguration, + moduleDescriptor: metricHostsModule, + }); + + const { fetchModuleDefinition, getIsJobDefinitionOutdated } = useInfraMLModuleDefinition({ + sourceConfiguration, + moduleDescriptor: metricHostsModule, + }); + + const hasOutdatedJobConfigurations = useMemo( + () => infraMLModule.jobSummaries.some(getIsJobConfigurationOutdated), + [getIsJobConfigurationOutdated, infraMLModule.jobSummaries] + ); + + const hasOutdatedJobDefinitions = useMemo( + () => infraMLModule.jobSummaries.some(getIsJobDefinitionOutdated), + [getIsJobDefinitionOutdated, infraMLModule.jobSummaries] + ); + + const hasStoppedJobs = useMemo( + () => + Object.values(infraMLModule.jobStatus).some( + (currentJobStatus) => currentJobStatus === 'stopped' + ), + [infraMLModule.jobStatus] + ); + + return { + ...infraMLModule, + fetchModuleDefinition, + hasOutdatedJobConfigurations, + hasOutdatedJobDefinitions, + hasStoppedJobs, + }; +}; + +export const [MetricK8sModuleProvider, useMetricK8sModuleContext] = createContainer( + useMetricK8sModule +); diff --git a/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module_descriptor.ts b/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module_descriptor.ts new file mode 100644 index 0000000000000..cbcff1c307af6 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module_descriptor.ts @@ -0,0 +1,129 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { ModuleDescriptor, ModuleSourceConfiguration } from '../../infra_ml_module_types'; +import { cleanUpJobsAndDatafeeds } from '../../infra_ml_cleanup'; +import { callJobsSummaryAPI } from '../../api/ml_get_jobs_summary_api'; +import { callGetMlModuleAPI } from '../../api/ml_get_module'; +import { callSetupMlModuleAPI } from '../../api/ml_setup_module_api'; +import { callValidateIndicesAPI } from '../../../logs/log_analysis/api/validate_indices'; +import { callValidateDatasetsAPI } from '../../../logs/log_analysis/api/validate_datasets'; +import { + metricsK8SJobTypes, + getJobId, + MetricK8sJobType, + DatasetFilter, + bucketSpan, + partitionField, +} from '../../../../../common/infra_ml'; + +const moduleId = 'metrics_ui_k8s'; +const moduleName = i18n.translate('xpack.infra.ml.metricsModuleName', { + defaultMessage: 'Metrics anomanly detection', +}); +const moduleDescription = i18n.translate('xpack.infra.ml.metricsHostModuleDescription', { + defaultMessage: 'Use Machine Learning to automatically detect anomalous log entry rates.', +}); + +const getJobIds = (spaceId: string, sourceId: string) => + metricsK8SJobTypes.reduce( + (accumulatedJobIds, jobType) => ({ + ...accumulatedJobIds, + [jobType]: getJobId(spaceId, sourceId, jobType), + }), + {} as Record + ); + +const getJobSummary = async (spaceId: string, sourceId: string) => { + const response = await callJobsSummaryAPI(spaceId, sourceId, metricsK8SJobTypes); + const jobIds = Object.values(getJobIds(spaceId, sourceId)); + + return response.filter((jobSummary) => jobIds.includes(jobSummary.id)); +}; + +const getModuleDefinition = async () => { + return await callGetMlModuleAPI(moduleId); +}; + +const setUpModule = async ( + start: number | undefined, + end: number | undefined, + datasetFilter: DatasetFilter, + { spaceId, sourceId, indices, timestampField }: ModuleSourceConfiguration, + pField?: string +) => { + const indexNamePattern = indices.join(','); + const jobIds = ['k8s_memory_usage', 'k8s_network_in', 'k8s_network_out']; + const jobOverrides = jobIds.map((id) => ({ + job_id: id, + analysis_config: { + bucket_span: `${bucketSpan}ms`, + }, + data_description: { + time_field: timestampField, + }, + custom_settings: { + metrics_source_config: { + indexPattern: indexNamePattern, + timestampField, + bucketSpan, + }, + }, + })); + + return callSetupMlModuleAPI( + moduleId, + start, + end, + spaceId, + sourceId, + indexNamePattern, + jobOverrides, + [] + ); +}; + +const cleanUpModule = async (spaceId: string, sourceId: string) => { + return await cleanUpJobsAndDatafeeds(spaceId, sourceId, metricsK8SJobTypes); +}; + +const validateSetupIndices = async (indices: string[], timestampField: string) => { + return await callValidateIndicesAPI(indices, [ + { + name: timestampField, + validTypes: ['date'], + }, + { + name: partitionField, + validTypes: ['keyword'], + }, + ]); +}; + +const validateSetupDatasets = async ( + indices: string[], + timestampField: string, + startTime: number, + endTime: number +) => { + return await callValidateDatasetsAPI(indices, timestampField, startTime, endTime); +}; + +export const metricHostsModule: ModuleDescriptor = { + moduleId, + moduleName, + moduleDescription, + jobTypes: metricsK8SJobTypes, + bucketSpan, + getJobIds, + getJobSummary, + getModuleDefinition, + setUpModule, + cleanUpModule, + validateSetupDatasets, + validateSetupIndices, +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/index.tsx b/x-pack/plugins/infra/public/pages/metrics/index.tsx index 3b3ed80f9e731..ac2c87248ae77 100644 --- a/x-pack/plugins/infra/public/pages/metrics/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/index.tsx @@ -38,6 +38,8 @@ import { MetricsAlertDropdown } from '../../alerting/metric_threshold/components import { SavedView } from '../../containers/saved_view/saved_view'; import { SourceConfigurationFields } from '../../graphql/types'; import { AlertPrefillProvider } from '../../alerting/use_alert_prefill'; +import { InfraMLCapabilitiesProvider } from '../../containers/ml/infra_ml_capabilities'; +import { AnomalyDetectionFlyout } from './inventory_view/components/ml/anomaly_detection/anomoly_detection_flyout'; const ADD_DATA_LABEL = i18n.translate('xpack.infra.metricsHeaderAddDataButtonLabel', { defaultMessage: 'Add data', @@ -55,110 +57,118 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => { - - + + + - + -
+ - - - - - - - - - - - - {ADD_DATA_LABEL} - - - - - - - - ( - - {({ configuration, createDerivedIndexPattern }) => ( - - - {configuration ? ( - - ) : ( - - )} - - )} - + } )} - /> - - - + > + + + + + + + + + + + + + + {ADD_DATA_LABEL} + + + + + + + + ( + + {({ configuration, createDerivedIndexPattern }) => ( + + + {configuration ? ( + + ) : ( + + )} + + )} + + )} + /> + + + + diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/bottom_drawer.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/bottom_drawer.tsx new file mode 100644 index 0000000000000..9cb84c7fff438 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/bottom_drawer.tsx @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useCallback, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty, EuiSpacer } from '@elastic/eui'; + +import { euiStyled, useUiTracker } from '../../../../../../observability/public'; +import { InfraFormatter } from '../../../../lib/lib'; +import { Timeline } from './timeline/timeline'; + +const showHistory = i18n.translate('xpack.infra.showHistory', { + defaultMessage: 'Show history', +}); +const hideHistory = i18n.translate('xpack.infra.hideHistory', { + defaultMessage: 'Hide history', +}); + +const TRANSITION_MS = 300; + +export const BottomDrawer: React.FC<{ + measureRef: (instance: HTMLElement | null) => void; + interval: string; + formatter: InfraFormatter; +}> = ({ measureRef, interval, formatter, children }) => { + const [isOpen, setIsOpen] = useState(false); + + const trackDrawerOpen = useUiTracker({ app: 'infra_metrics' }); + const onClick = useCallback(() => { + if (!isOpen) trackDrawerOpen({ metric: 'open_timeline_drawer__inventory' }); + setIsOpen(!isOpen); + }, [isOpen, trackDrawerOpen]); + + return ( + + + + + {isOpen ? hideHistory : showHistory} + + + + {children} + + + + + + + + + + ); +}; + +const BottomActionContainer = euiStyled.div<{ isOpen: boolean }>` + padding: ${(props) => props.theme.eui.paddingSizes.m} 0; + position: fixed; + left: 0; + bottom: 0; + right: 0; + transition: transform ${TRANSITION_MS}ms; + transform: translateY(${(props) => (props.isOpen ? 0 : '224px')}) +`; + +const BottomActionTopBar = euiStyled(EuiFlexGroup).attrs({ + justifyContent: 'spaceBetween', + alignItems: 'center', +})` + margin-bottom: 0; + height: 48px; +`; + +const ShowHideButton = euiStyled(EuiButtonEmpty).attrs({ size: 's' })` + width: 140px; +`; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx index 47616c7f4f7fd..712578be7dffd 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx @@ -7,7 +7,7 @@ import React, { useCallback, useEffect } from 'react'; import { useInterval } from 'react-use'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { AutoSizer } from '../../../../components/auto_sizer'; import { convertIntervalToString } from '../../../../utils/convert_interval_to_string'; import { NodesOverview } from './nodes_overview'; @@ -23,12 +23,13 @@ import { euiStyled } from '../../../../../../observability/public'; import { Toolbar } from './toolbars/toolbar'; import { ViewSwitcher } from './waffle/view_switcher'; import { IntervalLabel } from './waffle/interval_label'; -import { Legend } from './waffle/legend'; import { createInventoryMetricFormatter } from '../lib/create_inventory_metric_formatter'; import { createLegend } from '../lib/create_legend'; import { useSavedViewContext } from '../../../../containers/saved_view/saved_view'; import { useWaffleViewState } from '../hooks/use_waffle_view_state'; import { SavedViewsToolbarControls } from '../../../../components/saved_views/toolbar_control'; +import { BottomDrawer } from './bottom_drawer'; +import { Legend } from './waffle/legend'; export const Layout = () => { const { sourceId, source } = useSourceContext(); @@ -104,12 +105,19 @@ export const Layout = () => { - + + + + + + + + {({ measureRef, bounds: { height = 0 } }) => ( @@ -128,24 +136,14 @@ export const Layout = () => { formatter={formatter} bottomMargin={height} /> - - - - - - - - - - - - - + + + )} @@ -164,12 +162,8 @@ const TopActionContainer = euiStyled.div` padding: ${(props) => `12px ${props.theme.eui.paddingSizes.m}`}; `; -const BottomActionContainer = euiStyled.div` - background-color: ${(props) => props.theme.eui.euiPageBackgroundColor}; - padding: ${(props) => props.theme.eui.paddingSizes.m} ${(props) => - props.theme.eui.paddingSizes.m}; - position: fixed; - left: 0; - bottom: 0; - right: 0; +const SavedViewContainer = euiStyled.div` + position: relative; + z-index: 1; + padding-left: ${(props) => props.theme.eui.paddingSizes.m}; `; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomoly_detection_flyout.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomoly_detection_flyout.tsx new file mode 100644 index 0000000000000..b063713fa2c97 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomoly_detection_flyout.tsx @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState, useCallback } from 'react'; +import { EuiButtonEmpty, EuiFlyout } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { FlyoutHome } from './flyout_home'; +import { JobSetupScreen } from './job_setup_screen'; +import { useInfraMLCapabilities } from '../../../../../../containers/ml/infra_ml_capabilities'; +import { MetricHostsModuleProvider } from '../../../../../../containers/ml/modules/metrics_hosts/module'; +import { MetricK8sModuleProvider } from '../../../../../../containers/ml/modules/metrics_k8s/module'; +import { useSourceViaHttp } from '../../../../../../containers/source/use_source_via_http'; +import { useActiveKibanaSpace } from '../../../../../../hooks/use_kibana_space'; + +export const AnomalyDetectionFlyout = () => { + const { hasInfraMLSetupCapabilities } = useInfraMLCapabilities(); + const [showFlyout, setShowFlyout] = useState(false); + const [screenName, setScreenName] = useState<'home' | 'setup'>('home'); + const [screenParams, setScreenParams] = useState(null); + const { source } = useSourceViaHttp({ + sourceId: 'default', + type: 'metrics', + }); + + const { space } = useActiveKibanaSpace(); + + const openFlyout = useCallback(() => { + setScreenName('home'); + setShowFlyout(true); + }, []); + + const openJobSetup = useCallback( + (jobType: 'hosts' | 'kubernetes') => { + setScreenName('setup'); + setScreenParams({ jobType }); + }, + [setScreenName] + ); + + const closeFlyout = useCallback(() => { + setShowFlyout(false); + }, []); + + if (source?.configuration.metricAlias == null || space == null) { + return null; + } + + return ( + <> + + + + {showFlyout && ( + + + + {screenName === 'home' && ( + + )} + {screenName === 'setup' && ( + + )} + + + + )} + + ); +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/flyout_home.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/flyout_home.tsx new file mode 100644 index 0000000000000..9cf898b684336 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/flyout_home.tsx @@ -0,0 +1,333 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState, useCallback, useEffect } from 'react'; +import { EuiFlyoutHeader, EuiTitle, EuiFlyoutBody, EuiTabs, EuiTab, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiText, EuiFlexGroup, EuiFlexItem, EuiCard, EuiIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { EuiCallOut } from '@elastic/eui'; +import { EuiButton } from '@elastic/eui'; +import { EuiButtonEmpty } from '@elastic/eui'; +import moment from 'moment'; +import { useInfraMLCapabilitiesContext } from '../../../../../../containers/ml/infra_ml_capabilities'; +import { SubscriptionSplashContent } from './subscription_splash_content'; +import { + MissingResultsPrivilegesPrompt, + MissingSetupPrivilegesPrompt, +} from '../../../../../../components/logging/log_analysis_setup'; +import { useMetricHostsModuleContext } from '../../../../../../containers/ml/modules/metrics_hosts/module'; +import { useMetricK8sModuleContext } from '../../../../../../containers/ml/modules/metrics_k8s/module'; +import { LoadingPage } from '../../../../../../components/loading_page'; +import { useLinkProps } from '../../../../../../hooks/use_link_props'; + +interface Props { + hasSetupCapabilities: boolean; + goToSetup(type: 'hosts' | 'kubernetes'): void; +} + +export const FlyoutHome = (props: Props) => { + const [tab, setTab] = useState<'jobs' | 'anomalies'>('jobs'); + const { goToSetup } = props; + const { + fetchJobStatus: fetchHostJobStatus, + setupStatus: hostSetupStatus, + jobSummaries: hostJobSummaries, + } = useMetricHostsModuleContext(); + const { + fetchJobStatus: fetchK8sJobStatus, + setupStatus: k8sSetupStatus, + jobSummaries: k8sJobSummaries, + } = useMetricK8sModuleContext(); + const { + hasInfraMLCapabilites, + hasInfraMLReadCapabilities, + hasInfraMLSetupCapabilities, + } = useInfraMLCapabilitiesContext(); + + const createHosts = useCallback(() => { + goToSetup('hosts'); + }, [goToSetup]); + + const createK8s = useCallback(() => { + goToSetup('kubernetes'); + }, [goToSetup]); + + const goToJobs = useCallback(() => { + setTab('jobs'); + }, []); + + const jobIds = [ + ...(k8sJobSummaries || []).map((k) => k.id), + ...(hostJobSummaries || []).map((h) => h.id), + ]; + const anomaliesUrl = useLinkProps({ + app: 'ml', + pathname: `/explorer?_g=${createResultsUrl(jobIds)}`, + }); + + useEffect(() => { + if (hasInfraMLReadCapabilities) { + fetchHostJobStatus(); + fetchK8sJobStatus(); + } + }, [fetchK8sJobStatus, fetchHostJobStatus, hasInfraMLReadCapabilities]); + + if (!hasInfraMLCapabilites) { + return ; + } else if (!hasInfraMLReadCapabilities) { + return ; + } else if (hostSetupStatus.type === 'initializing' || k8sSetupStatus.type === 'initializing') { + return ( + + ); + } else if (!hasInfraMLSetupCapabilities) { + return ; + } else { + return ( + <> + + +

+ +

+
+
+ + + + + + + + + + + + {hostJobSummaries.length > 0 && ( + <> + 0} + hasK8sJobs={k8sJobSummaries.length > 0} + /> + + + )} + {tab === 'jobs' && ( + 0} + hasK8sJobs={k8sJobSummaries.length > 0} + hasSetupCapabilities={props.hasSetupCapabilities} + createHosts={createHosts} + createK8s={createK8s} + /> + )} + + + ); + } +}; + +interface CalloutProps { + hasHostJobs: boolean; + hasK8sJobs: boolean; +} +const JobsEnabledCallout = (props: CalloutProps) => { + let target = ''; + if (props.hasHostJobs && props.hasK8sJobs) { + target = `${i18n.translate('xpack.infra.ml.anomalyFlyout.create.hostTitle', { + defaultMessage: 'Hosts', + })} and ${i18n.translate('xpack.infra.ml.anomalyFlyout.create.k8sSuccessTitle', { + defaultMessage: 'Kubernetes', + })}`; + } else if (props.hasHostJobs) { + target = i18n.translate('xpack.infra.ml.anomalyFlyout.create.hostSuccessTitle', { + defaultMessage: 'Hosts', + }); + } else if (props.hasK8sJobs) { + target = i18n.translate('xpack.infra.ml.anomalyFlyout.create.k8sSuccessTitle', { + defaultMessage: 'Kubernetes', + }); + } + + const manageJobsLinkProps = useLinkProps({ + app: 'ml', + pathname: '/jobs', + }); + + return ( + <> + + } + iconType="check" + /> + + + + + + ); +}; + +interface CreateJobTab { + hasSetupCapabilities: boolean; + hasHostJobs: boolean; + hasK8sJobs: boolean; + createHosts(): void; + createK8s(): void; +} + +const CreateJobTab = (props: CreateJobTab) => { + return ( + <> +
+ +

+ +

+
+ +

+ +

+
+
+ + + + + } + // title="Hosts" + title={ + + } + description={ + + } + footer={ + <> + {props.hasHostJobs && ( + + + + )} + {!props.hasHostJobs && ( + + + + )} + + } + /> + + + } + title={ + + } + description={ + + } + footer={ + <> + {props.hasK8sJobs && ( + + + + )} + {!props.hasK8sJobs && ( + + + + )} + + } + /> + + + + ); +}; + +function createResultsUrl(jobIds: string[], mode = 'absolute') { + const idString = jobIds.map((j) => `'${j}'`).join(','); + let path = ''; + + const from = moment().subtract(4, 'weeks').toISOString(); + const to = moment().toISOString(); + + path += `(ml:(jobIds:!(${idString}))`; + path += `,refreshInterval:(display:Off,pause:!f,value:0),time:(from:'${from}'`; + path += `,to:'${to}'`; + if (mode === 'invalid') { + path += `,mode:invalid`; + } + path += "))&_a=(query:(query_string:(analyze_wildcard:!t,query:'*')))"; + + return path; +} diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/job_setup_screen.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/job_setup_screen.tsx new file mode 100644 index 0000000000000..730cd7b6e9ef5 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/job_setup_screen.tsx @@ -0,0 +1,277 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState, useCallback, useMemo, useEffect } from 'react'; +import { EuiForm, EuiDescribedFormGroup, EuiFormRow } from '@elastic/eui'; +import { EuiText, EuiSpacer } from '@elastic/eui'; +import { EuiFlyoutHeader, EuiTitle, EuiFlyoutBody } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiFlyoutFooter } from '@elastic/eui'; +import { EuiButton } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; +import moment, { Moment } from 'moment'; +import { EuiComboBox } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { EuiLoadingSpinner } from '@elastic/eui'; +import { useSourceViaHttp } from '../../../../../../containers/source/use_source_via_http'; +import { useMetricK8sModuleContext } from '../../../../../../containers/ml/modules/metrics_k8s/module'; +import { useMetricHostsModuleContext } from '../../../../../../containers/ml/modules/metrics_hosts/module'; +import { FixedDatePicker } from '../../../../../../components/fixed_datepicker'; + +interface Props { + jobType: 'hosts' | 'kubernetes'; + closeFlyout(): void; + goHome(): void; +} + +export const JobSetupScreen = (props: Props) => { + const [now] = useState(() => moment()); + const { goHome } = props; + const [startDate, setStartDate] = useState(now.clone().subtract(4, 'weeks')); + const [partitionField, setPartitionField] = useState(null); + const h = useMetricHostsModuleContext(); + const k = useMetricK8sModuleContext(); + const { createDerivedIndexPattern } = useSourceViaHttp({ + sourceId: 'default', + type: 'metrics', + }); + + const indicies = h.sourceConfiguration.indices; + + const setupStatus = useMemo(() => { + if (props.jobType === 'kubernetes') { + return k.setupStatus; + } else { + return h.setupStatus; + } + }, [props.jobType, k.setupStatus, h.setupStatus]); + + const cleanUpAndSetUpModule = useMemo(() => { + if (props.jobType === 'kubernetes') { + return k.cleanUpAndSetUpModule; + } else { + return h.cleanUpAndSetUpModule; + } + }, [props.jobType, k.cleanUpAndSetUpModule, h.cleanUpAndSetUpModule]); + + const setUpModule = useMemo(() => { + if (props.jobType === 'kubernetes') { + return k.setUpModule; + } else { + return h.setUpModule; + } + }, [props.jobType, k.setUpModule, h.setUpModule]); + + const hasSummaries = useMemo(() => { + if (props.jobType === 'kubernetes') { + return k.jobSummaries.length > 0; + } else { + return h.jobSummaries.length > 0; + } + }, [props.jobType, k.jobSummaries, h.jobSummaries]); + + const derivedIndexPattern = useMemo(() => createDerivedIndexPattern('metrics'), [ + createDerivedIndexPattern, + ]); + + const updateStart = useCallback((date: Moment) => { + setStartDate(date); + }, []); + + const createJobs = useCallback(() => { + if (hasSummaries) { + cleanUpAndSetUpModule( + indicies, + moment(startDate).toDate().getTime(), + undefined, + { type: 'includeAll' }, + partitionField ? partitionField[0] : undefined + ); + } else { + setUpModule( + indicies, + moment(startDate).toDate().getTime(), + undefined, + { type: 'includeAll' }, + partitionField ? partitionField[0] : undefined + ); + } + }, [cleanUpAndSetUpModule, setUpModule, hasSummaries, indicies, partitionField, startDate]); + + const onPartitionFieldChange = useCallback((value: Array<{ label: string }>) => { + setPartitionField(value.map((v) => v.label)); + }, []); + + useEffect(() => { + if (props.jobType === 'kubernetes') { + setPartitionField(['kubernetes.namespace']); + } + }, [props.jobType]); + + useEffect(() => { + if (setupStatus.type === 'succeeded') { + goHome(); + } + }, [setupStatus, goHome]); + + return ( + <> + + +

+ +

+
+
+ + {setupStatus.type === 'pending' ? ( + + + + + + + + + ) : setupStatus.type === 'failed' ? ( + <> + + + + + + + ) : ( + <> + +

+ +

+
+ + + + + + + } + description={ + + } + > + + } + > + + + + + + + + } + description={ + + } + > + + } + compressed + > + ({ label: p })) : undefined + } + options={derivedIndexPattern.fields + .filter((f) => f.aggregatable && f.type === 'string') + .map((f) => ({ label: f.name }))} + onChange={onPartitionFieldChange} + isClearable={true} + /> + + + + + )} +
+ + + + + + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/subscription_splash_content.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/subscription_splash_content.tsx new file mode 100644 index 0000000000000..f07c37f5e7ea2 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/subscription_splash_content.tsx @@ -0,0 +1,172 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiPage, + EuiPageBody, + EuiPageContent, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiTitle, + EuiText, + EuiButton, + EuiButtonEmpty, + EuiImage, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { LoadingPage } from '../../../../../../components/loading_page'; +import { useTrialStatus } from '../../../../../../hooks/use_trial_status'; +import { useKibana } from '../../../../../../../../../../src/plugins/kibana_react/public'; +import { euiStyled } from '../../../../../../../../observability/public'; +import { HttpStart } from '../../../../../../../../../../src/core/public'; + +export const SubscriptionSplashContent: React.FC = () => { + const { services } = useKibana<{ http: HttpStart }>(); + const { loadState, isTrialAvailable, checkTrialAvailability } = useTrialStatus(); + + useEffect(() => { + checkTrialAvailability(); + }, [checkTrialAvailability]); + + if (loadState === 'pending') { + return ( + + ); + } + + const canStartTrial = isTrialAvailable && loadState === 'resolved'; + + let title; + let description; + let cta; + + if (canStartTrial) { + title = ( + + ); + + description = ( + + ); + + cta = ( + + + + ); + } else { + title = ( + + ); + + description = ( + + ); + + cta = ( + + + + ); + } + + return ( + + + + + + +

{title}

+
+ + +

{description}

+
+ +
{cta}
+
+ + + +
+ + +

+ +

+
+ + + +
+
+
+
+ ); +}; + +const SubscriptionPage = euiStyled(EuiPage)` + height: 100% +`; + +const SubscriptionPageContent = euiStyled(EuiPageContent)` + max-width: 768px !important; +`; + +const SubscriptionPageFooter = euiStyled.div` + background: ${(props) => props.theme.eui.euiColorLightestShade}; + margin: 0 -${(props) => props.theme.eui.paddingSizes.l} -${(props) => + props.theme.eui.paddingSizes.l}; + padding: ${(props) => props.theme.eui.paddingSizes.l}; +`; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/timeline/timeline.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/timeline/timeline.tsx new file mode 100644 index 0000000000000..2792b6eb18b00 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/timeline/timeline.tsx @@ -0,0 +1,228 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useMemo, useCallback } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import moment from 'moment'; +import { first, last } from 'lodash'; +import { EuiLoadingChart, EuiText, EuiEmptyPrompt, EuiButton } from '@elastic/eui'; +import { + Axis, + Chart, + Settings, + Position, + TooltipValue, + niceTimeFormatter, + ElementClickListener, +} from '@elastic/charts'; +import { useUiSetting } from '../../../../../../../../../src/plugins/kibana_react/public'; +import { toMetricOpt } from '../../../../../../common/snapshot_metric_i18n'; +import { MetricsExplorerAggregation } from '../../../../../../common/http_api'; +import { Color } from '../../../../../../common/color_palette'; +import { useSourceContext } from '../../../../../containers/source'; +import { useTimeline } from '../../hooks/use_timeline'; +import { useWaffleOptionsContext } from '../../hooks/use_waffle_options'; +import { useWaffleTimeContext } from '../../hooks/use_waffle_time'; +import { useWaffleFiltersContext } from '../../hooks/use_waffle_filters'; +import { MetricExplorerSeriesChart } from '../../../metrics_explorer/components/series_chart'; +import { MetricsExplorerChartType } from '../../../metrics_explorer/hooks/use_metrics_explorer_options'; +import { getTimelineChartTheme } from '../../../metrics_explorer/components/helpers/get_chart_theme'; +import { calculateDomain } from '../../../metrics_explorer/components/helpers/calculate_domain'; + +import { euiStyled } from '../../../../../../../observability/public'; +import { InfraFormatter } from '../../../../../lib/lib'; + +interface Props { + interval: string; + yAxisFormatter: InfraFormatter; + isVisible: boolean; +} + +export const Timeline: React.FC = ({ interval, yAxisFormatter, isVisible }) => { + const { sourceId } = useSourceContext(); + const { metric, nodeType, accountId, region } = useWaffleOptionsContext(); + const { currentTime, jumpToTime, stopAutoReload } = useWaffleTimeContext(); + const { filterQueryAsJson } = useWaffleFiltersContext(); + const { loading, error, timeseries, reload } = useTimeline( + filterQueryAsJson, + [metric], + nodeType, + sourceId, + currentTime, + accountId, + region, + interval, + isVisible + ); + + const metricLabel = toMetricOpt(metric.type)?.textLC; + + const chartMetric = { + color: Color.color0, + aggregation: 'avg' as MetricsExplorerAggregation, + label: metricLabel, + }; + + const dateFormatter = useMemo(() => { + if (!timeseries) return () => ''; + const firstTimestamp = first(timeseries.rows)?.timestamp; + const lastTimestamp = last(timeseries.rows)?.timestamp; + + if (firstTimestamp == null || lastTimestamp == null) { + return (value: number) => `${value}`; + } + + return niceTimeFormatter([firstTimestamp, lastTimestamp]); + }, [timeseries]); + + const isDarkMode = useUiSetting('theme:darkMode'); + const tooltipProps = { + headerFormatter: (tooltipValue: TooltipValue) => + moment(tooltipValue.value).format('Y-MM-DD HH:mm:ss.SSS'), + }; + + const dataDomain = timeseries ? calculateDomain(timeseries, [chartMetric], false) : null; + const domain = dataDomain + ? { + max: dataDomain.max * 1.1, // add 10% headroom. + min: dataDomain.min, + } + : { max: 0, min: 0 }; + + const onClickPoint: ElementClickListener = useCallback( + ([[geometryValue]]) => { + if (!Array.isArray(geometryValue)) { + const { x: timestamp } = geometryValue; + jumpToTime(timestamp); + stopAutoReload(); + } + }, + [jumpToTime, stopAutoReload] + ); + + if (loading) { + return ( + + + + + + ); + } + + if (!loading && (error || !timeseries)) { + return ( + + {error ? errorTitle : noHistoryDataTitle}} + actions={ + + {error ? retryButtonLabel : checkNewDataButtonLabel} + + } + /> + + ); + } + + return ( + + + + + + + + + + + + + + + + + + ); +}; + +const TimelineContainer = euiStyled.div` + background-color: ${(props) => props.theme.eui.euiPageBackgroundColor}; + border-top: 1px solid ${(props) => props.theme.eui.euiColorLightShade}; + height: 220px; + width: 100%; + padding: ${(props) => props.theme.eui.paddingSizes.s} ${(props) => + props.theme.eui.paddingSizes.m}; + display: flex; + flex-direction: column; +`; + +const TimelineHeader = euiStyled.div` + display: flex; + width: 100%; + padding: ${(props) => props.theme.eui.paddingSizes.s} ${(props) => + props.theme.eui.paddingSizes.m}; +`; + +const TimelineChartContainer = euiStyled.div` + padding-left: ${(props) => props.theme.eui.paddingSizes.xs}; + width: 100%; + height: 100%; +`; + +const TimelineLoadingContainer = euiStyled.div` + display: flex; + justify-content: center; + align-items: center; + height: 100%; +`; + +const noHistoryDataTitle = i18n.translate('xpack.infra.inventoryTimeline.noHistoryDataTitle', { + defaultMessage: 'There is no history data to display.', +}); + +const errorTitle = i18n.translate('xpack.infra.inventoryTimeline.errorTitle', { + defaultMessage: 'Unable to display history data.', +}); + +const checkNewDataButtonLabel = i18n.translate( + 'xpack.infra.inventoryTimeline.checkNewDataButtonLabel', + { + defaultMessage: 'Check for new data', + } +); + +const retryButtonLabel = i18n.translate('xpack.infra.inventoryTimeline.retryButtonLabel', { + defaultMessage: 'Try again', +}); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/custom_metric_form.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/custom_metric_form.tsx index a785cb31c3cf4..262d94d8f3674 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/custom_metric_form.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/custom_metric_form.tsx @@ -27,10 +27,7 @@ import { SNAPSHOT_CUSTOM_AGGREGATIONS, SnapshotCustomAggregationRT, } from '../../../../../../../common/http_api/snapshot_api'; -import { - EuiTheme, - withTheme, -} from '../../../../../../../../../legacy/common/eui_styled_components'; +import { EuiTheme, withTheme } from '../../../../../../../../xpack_legacy/common'; interface SelectedOption { label: string; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/metrics_edit_mode.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/metrics_edit_mode.tsx index e75885ccbc917..831a0cde49cfb 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/metrics_edit_mode.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/metrics_edit_mode.tsx @@ -8,10 +8,7 @@ import { EuiFlexItem, EuiFlexGroup, EuiButtonIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { getCustomMetricLabel } from '../../../../../../../common/formatters/get_custom_metric_label'; import { SnapshotCustomMetricInput } from '../../../../../../../common/http_api/snapshot_api'; -import { - EuiTheme, - withTheme, -} from '../../../../../../../../../legacy/common/eui_styled_components'; +import { EuiTheme, withTheme } from '../../../../../../../../xpack_legacy/common'; interface Props { theme: EuiTheme | undefined; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/mode_switcher.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/mode_switcher.tsx index d1abcade5d660..956241545e8be 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/mode_switcher.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/mode_switcher.tsx @@ -9,10 +9,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { CustomMetricMode } from './types'; import { SnapshotCustomMetricInput } from '../../../../../../../common/http_api/snapshot_api'; -import { - EuiTheme, - withTheme, -} from '../../../../../../../../../legacy/common/eui_styled_components'; +import { EuiTheme, withTheme } from '../../../../../../../../xpack_legacy/common'; interface Props { theme: EuiTheme | undefined; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_hosts_anomalies.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_hosts_anomalies.ts new file mode 100644 index 0000000000000..f755057d0b76d --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_hosts_anomalies.ts @@ -0,0 +1,318 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useMemo, useState, useCallback, useEffect, useReducer } from 'react'; +import { + INFA_ML_GET_METRICS_HOSTS_ANOMALIES_PATH, + Sort, + Pagination, + PaginationCursor, + getMetricsHostsAnomaliesRequestPayloadRT, + MetricsHostsAnomaly, + getMetricsHostsAnomaliesSuccessReponsePayloadRT, +} from '../../../../../common/http_api/infra_ml'; +import { useTrackedPromise } from '../../../../utils/use_tracked_promise'; +import { npStart } from '../../../../legacy_singletons'; +import { decodeOrThrow } from '../../../../../common/runtime_types'; + +export type SortOptions = Sort; +export type PaginationOptions = Pick; +export type Page = number; +export type FetchNextPage = () => void; +export type FetchPreviousPage = () => void; +export type ChangeSortOptions = (sortOptions: Sort) => void; +export type ChangePaginationOptions = (paginationOptions: PaginationOptions) => void; +export type MetricsHostsAnomalies = MetricsHostsAnomaly[]; +interface PaginationCursors { + previousPageCursor: PaginationCursor; + nextPageCursor: PaginationCursor; +} + +interface ReducerState { + page: number; + lastReceivedCursors: PaginationCursors | undefined; + paginationCursor: Pagination['cursor'] | undefined; + hasNextPage: boolean; + paginationOptions: PaginationOptions; + sortOptions: Sort; + timeRange: { + start: number; + end: number; + }; + filteredDatasets?: string[]; +} + +type ReducerStateDefaults = Pick< + ReducerState, + 'page' | 'lastReceivedCursors' | 'paginationCursor' | 'hasNextPage' +>; + +type ReducerAction = + | { type: 'changePaginationOptions'; payload: { paginationOptions: PaginationOptions } } + | { type: 'changeSortOptions'; payload: { sortOptions: Sort } } + | { type: 'fetchNextPage' } + | { type: 'fetchPreviousPage' } + | { type: 'changeHasNextPage'; payload: { hasNextPage: boolean } } + | { type: 'changeLastReceivedCursors'; payload: { lastReceivedCursors: PaginationCursors } } + | { type: 'changeTimeRange'; payload: { timeRange: { start: number; end: number } } } + | { type: 'changeFilteredDatasets'; payload: { filteredDatasets?: string[] } }; + +const stateReducer = (state: ReducerState, action: ReducerAction): ReducerState => { + const resetPagination = { + page: 1, + paginationCursor: undefined, + }; + switch (action.type) { + case 'changePaginationOptions': + return { + ...state, + ...resetPagination, + ...action.payload, + }; + case 'changeSortOptions': + return { + ...state, + ...resetPagination, + ...action.payload, + }; + case 'changeHasNextPage': + return { + ...state, + ...action.payload, + }; + case 'changeLastReceivedCursors': + return { + ...state, + ...action.payload, + }; + case 'fetchNextPage': + return state.lastReceivedCursors + ? { + ...state, + page: state.page + 1, + paginationCursor: { searchAfter: state.lastReceivedCursors.nextPageCursor }, + } + : state; + case 'fetchPreviousPage': + return state.lastReceivedCursors + ? { + ...state, + page: state.page - 1, + paginationCursor: { searchBefore: state.lastReceivedCursors.previousPageCursor }, + } + : state; + case 'changeTimeRange': + return { + ...state, + ...resetPagination, + ...action.payload, + }; + case 'changeFilteredDatasets': + return { + ...state, + ...resetPagination, + ...action.payload, + }; + default: + return state; + } +}; + +const STATE_DEFAULTS: ReducerStateDefaults = { + // NOTE: This piece of state is purely for the client side, it could be extracted out of the hook. + page: 1, + // Cursor from the last request + lastReceivedCursors: undefined, + // Cursor to use for the next request. For the first request, and therefore not paging, this will be undefined. + paginationCursor: undefined, + hasNextPage: false, +}; + +export const useMetricsHostsAnomaliesResults = ({ + endTime, + startTime, + sourceId, + defaultSortOptions, + defaultPaginationOptions, + onGetMetricsHostsAnomaliesDatasetsError, + filteredDatasets, +}: { + endTime: number; + startTime: number; + sourceId: string; + defaultSortOptions: Sort; + defaultPaginationOptions: Pick; + onGetMetricsHostsAnomaliesDatasetsError?: (error: Error) => void; + filteredDatasets?: string[]; +}) => { + const initStateReducer = (stateDefaults: ReducerStateDefaults): ReducerState => { + return { + ...stateDefaults, + paginationOptions: defaultPaginationOptions, + sortOptions: defaultSortOptions, + filteredDatasets, + timeRange: { + start: startTime, + end: endTime, + }, + }; + }; + + const [reducerState, dispatch] = useReducer(stateReducer, STATE_DEFAULTS, initStateReducer); + + const [metricsHostsAnomalies, setMetricsHostsAnomalies] = useState([]); + + const [getMetricsHostsAnomaliesRequest, getMetricsHostsAnomalies] = useTrackedPromise( + { + cancelPreviousOn: 'creation', + createPromise: async () => { + const { + timeRange: { start: queryStartTime, end: queryEndTime }, + sortOptions, + paginationOptions, + paginationCursor, + } = reducerState; + return await callGetMetricHostsAnomaliesAPI( + sourceId, + queryStartTime, + queryEndTime, + sortOptions, + { + ...paginationOptions, + cursor: paginationCursor, + } + ); + }, + onResolve: ({ data: { anomalies, paginationCursors: requestCursors, hasMoreEntries } }) => { + const { paginationCursor } = reducerState; + if (requestCursors) { + dispatch({ + type: 'changeLastReceivedCursors', + payload: { lastReceivedCursors: requestCursors }, + }); + } + // Check if we have more "next" entries. "Page" covers the "previous" scenario, + // since we need to know the page we're on anyway. + if (!paginationCursor || (paginationCursor && 'searchAfter' in paginationCursor)) { + dispatch({ type: 'changeHasNextPage', payload: { hasNextPage: hasMoreEntries } }); + } else if (paginationCursor && 'searchBefore' in paginationCursor) { + // We've requested a previous page, therefore there is a next page. + dispatch({ type: 'changeHasNextPage', payload: { hasNextPage: true } }); + } + setMetricsHostsAnomalies(anomalies); + }, + }, + [ + sourceId, + dispatch, + reducerState.timeRange, + reducerState.sortOptions, + reducerState.paginationOptions, + reducerState.paginationCursor, + reducerState.filteredDatasets, + ] + ); + + const changeSortOptions = useCallback( + (nextSortOptions: Sort) => { + dispatch({ type: 'changeSortOptions', payload: { sortOptions: nextSortOptions } }); + }, + [dispatch] + ); + + const changePaginationOptions = useCallback( + (nextPaginationOptions: PaginationOptions) => { + dispatch({ + type: 'changePaginationOptions', + payload: { paginationOptions: nextPaginationOptions }, + }); + }, + [dispatch] + ); + + // Time range has changed + useEffect(() => { + dispatch({ + type: 'changeTimeRange', + payload: { timeRange: { start: startTime, end: endTime } }, + }); + }, [startTime, endTime]); + + // Selected datasets have changed + useEffect(() => { + dispatch({ + type: 'changeFilteredDatasets', + payload: { filteredDatasets }, + }); + }, [filteredDatasets]); + + useEffect(() => { + getMetricsHostsAnomalies(); + }, [getMetricsHostsAnomalies]); // TODO: FIgure out the deps here. + + const handleFetchNextPage = useCallback(() => { + if (reducerState.lastReceivedCursors) { + dispatch({ type: 'fetchNextPage' }); + } + }, [dispatch, reducerState]); + + const handleFetchPreviousPage = useCallback(() => { + if (reducerState.lastReceivedCursors) { + dispatch({ type: 'fetchPreviousPage' }); + } + }, [dispatch, reducerState]); + + const isLoadingMetricsHostsAnomalies = useMemo( + () => getMetricsHostsAnomaliesRequest.state === 'pending', + [getMetricsHostsAnomaliesRequest.state] + ); + + const hasFailedLoadingMetricsHostsAnomalies = useMemo( + () => getMetricsHostsAnomaliesRequest.state === 'rejected', + [getMetricsHostsAnomaliesRequest.state] + ); + + return { + metricsHostsAnomalies, + getMetricsHostsAnomalies, + isLoadingMetricsHostsAnomalies, + hasFailedLoadingMetricsHostsAnomalies, + changeSortOptions, + sortOptions: reducerState.sortOptions, + changePaginationOptions, + paginationOptions: reducerState.paginationOptions, + fetchPreviousPage: reducerState.page > 1 ? handleFetchPreviousPage : undefined, + fetchNextPage: reducerState.hasNextPage ? handleFetchNextPage : undefined, + page: reducerState.page, + }; +}; + +export const callGetMetricHostsAnomaliesAPI = async ( + sourceId: string, + startTime: number, + endTime: number, + sort: Sort, + pagination: Pagination +) => { + const response = await npStart.http.fetch(INFA_ML_GET_METRICS_HOSTS_ANOMALIES_PATH, { + method: 'POST', + body: JSON.stringify( + getMetricsHostsAnomaliesRequestPayloadRT.encode({ + data: { + sourceId, + timeRange: { + startTime, + endTime, + }, + sort, + pagination, + }, + }) + ), + }); + + return decodeOrThrow(getMetricsHostsAnomaliesSuccessReponsePayloadRT)(response); +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_k8s_anomalies.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_k8s_anomalies.ts new file mode 100644 index 0000000000000..4a7b78e1fdf92 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_k8s_anomalies.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useMemo, useState, useCallback, useEffect, useReducer } from 'react'; +import { + Sort, + Pagination, + PaginationCursor, + INFA_ML_GET_METRICS_K8S_ANOMALIES_PATH, + getMetricsK8sAnomaliesSuccessReponsePayloadRT, + getMetricsK8sAnomaliesRequestPayloadRT, + MetricsK8sAnomaly, +} from '../../../../../common/http_api/infra_ml'; +import { useTrackedPromise } from '../../../../utils/use_tracked_promise'; +import { npStart } from '../../../../legacy_singletons'; +import { decodeOrThrow } from '../../../../../common/runtime_types'; + +export type SortOptions = Sort; +export type PaginationOptions = Pick; +export type Page = number; +export type FetchNextPage = () => void; +export type FetchPreviousPage = () => void; +export type ChangeSortOptions = (sortOptions: Sort) => void; +export type ChangePaginationOptions = (paginationOptions: PaginationOptions) => void; +export type MetricsK8sAnomalies = MetricsK8sAnomaly[]; +interface PaginationCursors { + previousPageCursor: PaginationCursor; + nextPageCursor: PaginationCursor; +} + +interface ReducerState { + page: number; + lastReceivedCursors: PaginationCursors | undefined; + paginationCursor: Pagination['cursor'] | undefined; + hasNextPage: boolean; + paginationOptions: PaginationOptions; + sortOptions: Sort; + timeRange: { + start: number; + end: number; + }; + filteredDatasets?: string[]; +} + +type ReducerStateDefaults = Pick< + ReducerState, + 'page' | 'lastReceivedCursors' | 'paginationCursor' | 'hasNextPage' +>; + +type ReducerAction = + | { type: 'changePaginationOptions'; payload: { paginationOptions: PaginationOptions } } + | { type: 'changeSortOptions'; payload: { sortOptions: Sort } } + | { type: 'fetchNextPage' } + | { type: 'fetchPreviousPage' } + | { type: 'changeHasNextPage'; payload: { hasNextPage: boolean } } + | { type: 'changeLastReceivedCursors'; payload: { lastReceivedCursors: PaginationCursors } } + | { type: 'changeTimeRange'; payload: { timeRange: { start: number; end: number } } } + | { type: 'changeFilteredDatasets'; payload: { filteredDatasets?: string[] } }; + +const stateReducer = (state: ReducerState, action: ReducerAction): ReducerState => { + const resetPagination = { + page: 1, + paginationCursor: undefined, + }; + switch (action.type) { + case 'changePaginationOptions': + return { + ...state, + ...resetPagination, + ...action.payload, + }; + case 'changeSortOptions': + return { + ...state, + ...resetPagination, + ...action.payload, + }; + case 'changeHasNextPage': + return { + ...state, + ...action.payload, + }; + case 'changeLastReceivedCursors': + return { + ...state, + ...action.payload, + }; + case 'fetchNextPage': + return state.lastReceivedCursors + ? { + ...state, + page: state.page + 1, + paginationCursor: { searchAfter: state.lastReceivedCursors.nextPageCursor }, + } + : state; + case 'fetchPreviousPage': + return state.lastReceivedCursors + ? { + ...state, + page: state.page - 1, + paginationCursor: { searchBefore: state.lastReceivedCursors.previousPageCursor }, + } + : state; + case 'changeTimeRange': + return { + ...state, + ...resetPagination, + ...action.payload, + }; + case 'changeFilteredDatasets': + return { + ...state, + ...resetPagination, + ...action.payload, + }; + default: + return state; + } +}; + +const STATE_DEFAULTS: ReducerStateDefaults = { + // NOTE: This piece of state is purely for the client side, it could be extracted out of the hook. + page: 1, + // Cursor from the last request + lastReceivedCursors: undefined, + // Cursor to use for the next request. For the first request, and therefore not paging, this will be undefined. + paginationCursor: undefined, + hasNextPage: false, +}; + +export const useMetricsK8sAnomaliesResults = ({ + endTime, + startTime, + sourceId, + defaultSortOptions, + defaultPaginationOptions, + onGetMetricsHostsAnomaliesDatasetsError, + filteredDatasets, +}: { + endTime: number; + startTime: number; + sourceId: string; + defaultSortOptions: Sort; + defaultPaginationOptions: Pick; + onGetMetricsHostsAnomaliesDatasetsError?: (error: Error) => void; + filteredDatasets?: string[]; +}) => { + const initStateReducer = (stateDefaults: ReducerStateDefaults): ReducerState => { + return { + ...stateDefaults, + paginationOptions: defaultPaginationOptions, + sortOptions: defaultSortOptions, + filteredDatasets, + timeRange: { + start: startTime, + end: endTime, + }, + }; + }; + + const [reducerState, dispatch] = useReducer(stateReducer, STATE_DEFAULTS, initStateReducer); + + const [metricsK8sAnomalies, setMetricsK8sAnomalies] = useState([]); + + const [getMetricsK8sAnomaliesRequest, getMetricsK8sAnomalies] = useTrackedPromise( + { + cancelPreviousOn: 'creation', + createPromise: async () => { + const { + timeRange: { start: queryStartTime, end: queryEndTime }, + sortOptions, + paginationOptions, + paginationCursor, + filteredDatasets: queryFilteredDatasets, + } = reducerState; + return await callGetMetricsK8sAnomaliesAPI( + sourceId, + queryStartTime, + queryEndTime, + sortOptions, + { + ...paginationOptions, + cursor: paginationCursor, + }, + queryFilteredDatasets + ); + }, + onResolve: ({ data: { anomalies, paginationCursors: requestCursors, hasMoreEntries } }) => { + const { paginationCursor } = reducerState; + if (requestCursors) { + dispatch({ + type: 'changeLastReceivedCursors', + payload: { lastReceivedCursors: requestCursors }, + }); + } + // Check if we have more "next" entries. "Page" covers the "previous" scenario, + // since we need to know the page we're on anyway. + if (!paginationCursor || (paginationCursor && 'searchAfter' in paginationCursor)) { + dispatch({ type: 'changeHasNextPage', payload: { hasNextPage: hasMoreEntries } }); + } else if (paginationCursor && 'searchBefore' in paginationCursor) { + // We've requested a previous page, therefore there is a next page. + dispatch({ type: 'changeHasNextPage', payload: { hasNextPage: true } }); + } + setMetricsK8sAnomalies(anomalies); + }, + }, + [ + sourceId, + dispatch, + reducerState.timeRange, + reducerState.sortOptions, + reducerState.paginationOptions, + reducerState.paginationCursor, + reducerState.filteredDatasets, + ] + ); + + const changeSortOptions = useCallback( + (nextSortOptions: Sort) => { + dispatch({ type: 'changeSortOptions', payload: { sortOptions: nextSortOptions } }); + }, + [dispatch] + ); + + const changePaginationOptions = useCallback( + (nextPaginationOptions: PaginationOptions) => { + dispatch({ + type: 'changePaginationOptions', + payload: { paginationOptions: nextPaginationOptions }, + }); + }, + [dispatch] + ); + + // Time range has changed + useEffect(() => { + dispatch({ + type: 'changeTimeRange', + payload: { timeRange: { start: startTime, end: endTime } }, + }); + }, [startTime, endTime]); + + // Selected datasets have changed + useEffect(() => { + dispatch({ + type: 'changeFilteredDatasets', + payload: { filteredDatasets }, + }); + }, [filteredDatasets]); + + useEffect(() => { + getMetricsK8sAnomalies(); + }, [getMetricsK8sAnomalies]); + + const handleFetchNextPage = useCallback(() => { + if (reducerState.lastReceivedCursors) { + dispatch({ type: 'fetchNextPage' }); + } + }, [dispatch, reducerState]); + + const handleFetchPreviousPage = useCallback(() => { + if (reducerState.lastReceivedCursors) { + dispatch({ type: 'fetchPreviousPage' }); + } + }, [dispatch, reducerState]); + + const isLoadingMetricsK8sAnomalies = useMemo( + () => getMetricsK8sAnomaliesRequest.state === 'pending', + [getMetricsK8sAnomaliesRequest.state] + ); + + const hasFailedLoadingMetricsK8sAnomalies = useMemo( + () => getMetricsK8sAnomaliesRequest.state === 'rejected', + [getMetricsK8sAnomaliesRequest.state] + ); + + return { + metricsK8sAnomalies, + getMetricsK8sAnomalies, + isLoadingMetricsK8sAnomalies, + hasFailedLoadingMetricsK8sAnomalies, + changeSortOptions, + sortOptions: reducerState.sortOptions, + changePaginationOptions, + paginationOptions: reducerState.paginationOptions, + fetchPreviousPage: reducerState.page > 1 ? handleFetchPreviousPage : undefined, + fetchNextPage: reducerState.hasNextPage ? handleFetchNextPage : undefined, + page: reducerState.page, + }; +}; + +export const callGetMetricsK8sAnomaliesAPI = async ( + sourceId: string, + startTime: number, + endTime: number, + sort: Sort, + pagination: Pagination, + datasets?: string[] +) => { + const response = await npStart.http.fetch(INFA_ML_GET_METRICS_K8S_ANOMALIES_PATH, { + method: 'POST', + body: JSON.stringify( + getMetricsK8sAnomaliesRequestPayloadRT.encode({ + data: { + sourceId, + timeRange: { + startTime, + endTime, + }, + sort, + pagination, + datasets, + }, + }) + ), + }); + + return decodeOrThrow(getMetricsK8sAnomaliesSuccessReponsePayloadRT)(response); +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_snaphot.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_snaphot.ts index 06b53d531f53c..702213516c123 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_snaphot.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_snaphot.ts @@ -43,7 +43,7 @@ export function useSnapshot( const timerange: InfraTimerangeInput = { interval: '1m', to: currentTime, - from: currentTime - 360 * 1000, + from: currentTime - 1200 * 1000, lookbackSize: 20, }; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_timeline.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_timeline.ts new file mode 100644 index 0000000000000..acf9011ac7ddd --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_timeline.ts @@ -0,0 +1,132 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { first } from 'lodash'; +import { useEffect, useMemo, useCallback } from 'react'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getIntervalInSeconds } from '../../../../../server/utils/get_interval_in_seconds'; +import { throwErrors, createPlainError } from '../../../../../common/runtime_types'; +import { useHTTPRequest } from '../../../../hooks/use_http_request'; +import { + SnapshotNodeResponseRT, + SnapshotNodeResponse, + SnapshotRequest, + InfraTimerangeInput, +} from '../../../../../common/http_api/snapshot_api'; +import { + InventoryItemType, + SnapshotMetricType, +} from '../../../../../common/inventory_models/types'; + +const ONE_MINUTE = 60; +const ONE_HOUR = ONE_MINUTE * 60; +const ONE_DAY = ONE_HOUR * 24; +const ONE_WEEK = ONE_DAY * 7; +const ONE_MONTH = ONE_DAY * 30; + +const getDisplayInterval = (interval: string | undefined) => { + if (interval) { + const intervalInSeconds = getIntervalInSeconds(interval); + if (intervalInSeconds < 300) return '5m'; + } + return interval; +}; + +const getTimeLengthFromInterval = (interval: string | undefined) => { + if (interval) { + const intervalInSeconds = getIntervalInSeconds(interval); + // Get up to 288 datapoints based on interval + const timeLength = + intervalInSeconds <= ONE_MINUTE * 15 + ? ONE_DAY + : intervalInSeconds <= ONE_MINUTE * 35 + ? ONE_DAY * 3 + : intervalInSeconds <= ONE_HOUR * 2.5 + ? ONE_WEEK + : ONE_MONTH; + return { timeLength, intervalInSeconds }; + } else { + return { timeLength: 0, intervalInSeconds: 0 }; + } +}; + +export function useTimeline( + filterQuery: string | null | undefined, + metrics: Array<{ type: SnapshotMetricType }>, + nodeType: InventoryItemType, + sourceId: string, + currentTime: number, + accountId: string, + region: string, + interval: string | undefined, + shouldReload: boolean +) { + const decodeResponse = (response: any) => { + return pipe( + SnapshotNodeResponseRT.decode(response), + fold(throwErrors(createPlainError), identity) + ); + }; + + const displayInterval = useMemo(() => getDisplayInterval(interval), [interval]); + + const timeLengthResult = useMemo(() => getTimeLengthFromInterval(displayInterval), [ + displayInterval, + ]); + const { timeLength, intervalInSeconds } = timeLengthResult; + + const timerange: InfraTimerangeInput = { + interval: displayInterval ?? '', + to: currentTime + intervalInSeconds * 1000, + from: currentTime - timeLength * 1000, + ignoreLookback: true, + forceInterval: true, + }; + + const { error, loading, response, makeRequest } = useHTTPRequest( + '/api/metrics/snapshot', + 'POST', + JSON.stringify({ + metrics, + groupBy: null, + nodeType, + timerange, + filterQuery, + sourceId, + accountId, + region, + includeTimeseries: true, + } as SnapshotRequest), + decodeResponse + ); + + const loadData = useCallback(() => { + if (shouldReload) return makeRequest(); + return Promise.resolve(); + }, [makeRequest, shouldReload]); + + useEffect(() => { + (async () => { + if (timeLength) { + await loadData(); + } + })(); + }, [loadData, timeLength]); + + const timeseries = response + ? first(response.nodes.map((node) => first(node.metrics)?.timeseries)) + : null; + + return { + error: (error && error.message) || null, + loading: !interval ? true : loading, + timeseries, + reload: makeRequest, + }; +} diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/get_chart_theme.ts b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/get_chart_theme.ts index 42469ffb5ee9a..bb6a70f65bb97 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/get_chart_theme.ts +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/get_chart_theme.ts @@ -4,8 +4,33 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Theme, LIGHT_THEME, DARK_THEME } from '@elastic/charts'; +import { + Theme, + PartialTheme, + LIGHT_THEME, + DARK_THEME, + mergeWithDefaultTheme, +} from '@elastic/charts'; export function getChartTheme(isDarkMode: boolean): Theme { return isDarkMode ? DARK_THEME : LIGHT_THEME; } + +export function getTimelineChartTheme(isDarkMode: boolean): Theme { + return isDarkMode ? DARK_THEME : mergeWithDefaultTheme(TIMELINE_LIGHT_THEME, LIGHT_THEME); +} + +const TIMELINE_LIGHT_THEME: PartialTheme = { + crosshair: { + band: { + fill: '#D3DAE6', + }, + }, + axes: { + gridLine: { + horizontal: { + stroke: '#eaeaea', + }, + }, + }, +}; diff --git a/x-pack/plugins/infra/server/infra_server.ts b/x-pack/plugins/infra/server/infra_server.ts index a72e40e25b479..206fffdd2e188 100644 --- a/x-pack/plugins/infra/server/infra_server.ts +++ b/x-pack/plugins/infra/server/infra_server.ts @@ -21,6 +21,8 @@ import { initGetLogEntryAnomaliesRoute, initGetLogEntryAnomaliesDatasetsRoute, } from './routes/log_analysis'; +import { initGetK8sAnomaliesRoute } from './routes/infra_ml'; +import { initGetHostsAnomaliesRoute } from './routes/infra_ml'; import { initMetricExplorerRoute } from './routes/metrics_explorer'; import { initMetadataRoute } from './routes/metadata'; import { initSnapshotRoute } from './routes/snapshot'; @@ -56,6 +58,8 @@ export const initInfraServer = (libs: InfraBackendLibs) => { initGetLogEntryRateRoute(libs); initGetLogEntryAnomaliesRoute(libs); initGetLogEntryAnomaliesDatasetsRoute(libs); + initGetK8sAnomaliesRoute(libs); + initGetHostsAnomaliesRoute(libs); initSnapshotRoute(libs); initNodeDetailsRoute(libs); initSourceRoute(libs); diff --git a/x-pack/plugins/infra/server/lib/infra_ml/common.ts b/x-pack/plugins/infra/server/lib/infra_ml/common.ts new file mode 100644 index 0000000000000..4d2be94c7cd62 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/infra_ml/common.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import type { MlAnomalyDetectors, MlSystem } from '../../types'; +import { NoLogAnalysisMlJobError } from './errors'; + +import { + CompositeDatasetKey, + createLogEntryDatasetsQuery, + LogEntryDatasetBucket, + logEntryDatasetsResponseRT, +} from './queries/log_entry_data_sets'; +import { decodeOrThrow } from '../../../common/runtime_types'; +import { startTracingSpan, TracingSpan } from '../../../common/performance_tracing'; + +export async function fetchMlJob(mlAnomalyDetectors: MlAnomalyDetectors, jobId: string) { + const finalizeMlGetJobSpan = startTracingSpan('Fetch ml job from ES'); + const { + jobs: [mlJob], + } = await mlAnomalyDetectors.jobs(jobId); + + const mlGetJobSpan = finalizeMlGetJobSpan(); + + if (mlJob == null) { + throw new NoLogAnalysisMlJobError(`Failed to find ml job ${jobId}.`); + } + + return { + mlJob, + timing: { + spans: [mlGetJobSpan], + }, + }; +} + +const COMPOSITE_AGGREGATION_BATCH_SIZE = 1000; + +// Finds datasets related to ML job ids +export async function getLogEntryDatasets( + mlSystem: MlSystem, + startTime: number, + endTime: number, + jobIds: string[] +) { + const finalizeLogEntryDatasetsSpan = startTracingSpan('get data sets'); + + let logEntryDatasetBuckets: LogEntryDatasetBucket[] = []; + let afterLatestBatchKey: CompositeDatasetKey | undefined; + let esSearchSpans: TracingSpan[] = []; + + while (true) { + const finalizeEsSearchSpan = startTracingSpan('fetch log entry dataset batch from ES'); + + const logEntryDatasetsResponse = decodeOrThrow(logEntryDatasetsResponseRT)( + await mlSystem.mlAnomalySearch( + createLogEntryDatasetsQuery( + jobIds, + startTime, + endTime, + COMPOSITE_AGGREGATION_BATCH_SIZE, + afterLatestBatchKey + ) + ) + ); + + const { after_key: afterKey, buckets: latestBatchBuckets = [] } = + logEntryDatasetsResponse.aggregations?.dataset_buckets ?? {}; + + logEntryDatasetBuckets = [...logEntryDatasetBuckets, ...latestBatchBuckets]; + afterLatestBatchKey = afterKey; + esSearchSpans = [...esSearchSpans, finalizeEsSearchSpan()]; + + if (latestBatchBuckets.length < COMPOSITE_AGGREGATION_BATCH_SIZE) { + break; + } + } + + const logEntryDatasetsSpan = finalizeLogEntryDatasetsSpan(); + + return { + data: logEntryDatasetBuckets.map((logEntryDatasetBucket) => logEntryDatasetBucket.key.dataset), + timing: { + spans: [logEntryDatasetsSpan, ...esSearchSpans], + }, + }; +} diff --git a/x-pack/plugins/infra/server/lib/infra_ml/errors.ts b/x-pack/plugins/infra/server/lib/infra_ml/errors.ts new file mode 100644 index 0000000000000..ad46ebf710266 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/infra_ml/errors.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable max-classes-per-file */ + +import { + UnknownMLCapabilitiesError, + InsufficientMLCapabilities, + MLPrivilegesUninitialized, +} from '../../../../ml/server'; + +export class NoLogAnalysisMlJobError extends Error { + constructor(message?: string) { + super(message); + Object.setPrototypeOf(this, new.target.prototype); + } +} + +export class InsufficientLogAnalysisMlJobConfigurationError extends Error { + constructor(message?: string) { + super(message); + Object.setPrototypeOf(this, new.target.prototype); + } +} + +export class UnknownCategoryError extends Error { + constructor(categoryId: number) { + super(`Unknown ml category ${categoryId}`); + Object.setPrototypeOf(this, new.target.prototype); + } +} + +export class InsufficientAnomalyMlJobsConfigured extends Error { + constructor(message?: string) { + super(message); + Object.setPrototypeOf(this, new.target.prototype); + } +} + +export const isMlPrivilegesError = (error: any) => { + return ( + error instanceof UnknownMLCapabilitiesError || + error instanceof InsufficientMLCapabilities || + error instanceof MLPrivilegesUninitialized + ); +}; diff --git a/x-pack/plugins/security_solution/server/graphql/tls/index.ts b/x-pack/plugins/infra/server/lib/infra_ml/index.ts similarity index 68% rename from x-pack/plugins/security_solution/server/graphql/tls/index.ts rename to x-pack/plugins/infra/server/lib/infra_ml/index.ts index 7d745742090a6..536f0a44d5890 100644 --- a/x-pack/plugins/security_solution/server/graphql/tls/index.ts +++ b/x-pack/plugins/infra/server/lib/infra_ml/index.ts @@ -4,5 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { createTlsResolvers } from './resolvers'; -export { tlsSchema } from './schema.gql'; +export * from './errors'; +export * from './metrics_hosts_anomalies'; +export * from './metrics_k8s_anomalies'; diff --git a/x-pack/plugins/infra/server/lib/infra_ml/metrics_hosts_anomalies.ts b/x-pack/plugins/infra/server/lib/infra_ml/metrics_hosts_anomalies.ts new file mode 100644 index 0000000000000..e0afa458aac88 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/infra_ml/metrics_hosts_anomalies.ts @@ -0,0 +1,289 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RequestHandlerContext } from 'src/core/server'; +import { InfraRequestHandlerContext } from '../../types'; +import { TracingSpan, startTracingSpan } from '../../../common/performance_tracing'; +import { fetchMlJob, getLogEntryDatasets } from './common'; +import { getJobId, metricsHostsJobTypes } from '../../../common/infra_ml'; +import { Sort, Pagination } from '../../../common/http_api/infra_ml'; +import type { MlSystem, MlAnomalyDetectors } from '../../types'; +import { InsufficientAnomalyMlJobsConfigured, isMlPrivilegesError } from './errors'; +import { decodeOrThrow } from '../../../common/runtime_types'; +import { + metricsHostsAnomaliesResponseRT, + createMetricsHostsAnomaliesQuery, +} from './queries/metrics_hosts_anomalies'; + +interface MappedAnomalyHit { + id: string; + anomalyScore: number; + dataset: string; + typical: number; + actual: number; + jobId: string; + startTime: number; + duration: number; + hostName: string[]; + categoryId?: string; +} + +async function getCompatibleAnomaliesJobIds( + spaceId: string, + sourceId: string, + mlAnomalyDetectors: MlAnomalyDetectors +) { + const metricsHostsJobIds = metricsHostsJobTypes.map((jt) => getJobId(spaceId, sourceId, jt)); + + const jobIds: string[] = []; + let jobSpans: TracingSpan[] = []; + + try { + await Promise.all( + metricsHostsJobIds.map((id) => { + return (async () => { + const { + timing: { spans }, + } = await fetchMlJob(mlAnomalyDetectors, id); + jobIds.push(id); + jobSpans = [...jobSpans, ...spans]; + })(); + }) + ); + } catch (e) { + if (isMlPrivilegesError(e)) { + throw e; + } + // An error is also thrown when no jobs are found + } + + return { + jobIds, + timing: { spans: jobSpans }, + }; +} + +export async function getMetricsHostsAnomalies( + context: RequestHandlerContext & { infra: Required }, + sourceId: string, + startTime: number, + endTime: number, + sort: Sort, + pagination: Pagination +) { + const finalizeMetricsHostsAnomaliesSpan = startTracingSpan('get metrics hosts entry anomalies'); + + const { + jobIds, + timing: { spans: jobSpans }, + } = await getCompatibleAnomaliesJobIds( + context.infra.spaceId, + sourceId, + context.infra.mlAnomalyDetectors + ); + + if (jobIds.length === 0) { + throw new InsufficientAnomalyMlJobsConfigured( + 'Metrics Hosts ML jobs need to be configured to search anomalies' + ); + } + + try { + const { + anomalies, + paginationCursors, + hasMoreEntries, + timing: { spans: fetchLogEntryAnomaliesSpans }, + } = await fetchMetricsHostsAnomalies( + context.infra.mlSystem, + jobIds, + startTime, + endTime, + sort, + pagination + ); + + const data = anomalies.map((anomaly) => { + const { jobId } = anomaly; + + return parseAnomalyResult(anomaly, jobId); + }); + + const metricsHostsAnomaliesSpan = finalizeMetricsHostsAnomaliesSpan(); + + return { + data, + paginationCursors, + hasMoreEntries, + timing: { + spans: [metricsHostsAnomaliesSpan, ...jobSpans, ...fetchLogEntryAnomaliesSpans], + }, + }; + } catch (e) { + throw new Error(e); + } +} + +const parseAnomalyResult = (anomaly: MappedAnomalyHit, jobId: string) => { + const { + id, + anomalyScore, + dataset, + typical, + actual, + duration, + hostName, + startTime: anomalyStartTime, + } = anomaly; + + return { + id, + anomalyScore, + dataset, + typical, + actual, + duration, + hostName, + startTime: anomalyStartTime, + type: 'metrics_hosts' as const, + jobId, + }; +}; + +async function fetchMetricsHostsAnomalies( + mlSystem: MlSystem, + jobIds: string[], + startTime: number, + endTime: number, + sort: Sort, + pagination: Pagination +) { + // We'll request 1 extra entry on top of our pageSize to determine if there are + // more entries to be fetched. This avoids scenarios where the client side can't + // determine if entries.length === pageSize actually means there are more entries / next page + // or not. + const expandedPagination = { ...pagination, pageSize: pagination.pageSize + 1 }; + + const finalizeFetchLogEntryAnomaliesSpan = startTracingSpan('fetch metrics hosts anomalies'); + + // console.log( + // 'data', + // JSON.stringify( + // await mlSystem.mlAnomalySearch( + // createMetricsHostsAnomaliesQuery(jobIds, startTime, endTime, sort, expandedPagination) + // ), + // null, + // 2 + // ) + // ); + const results = decodeOrThrow(metricsHostsAnomaliesResponseRT)( + await mlSystem.mlAnomalySearch( + createMetricsHostsAnomaliesQuery(jobIds, startTime, endTime, sort, expandedPagination) + ) + ); + + const { + hits: { hits }, + } = results; + const hasMoreEntries = hits.length > pagination.pageSize; + + // An extra entry was found and hasMoreEntries has been determined, the extra entry can be removed. + if (hasMoreEntries) { + hits.pop(); + } + + // To "search_before" the sort order will have been reversed for ES. + // The results are now reversed back, to match the requested sort. + if (pagination.cursor && 'searchBefore' in pagination.cursor) { + hits.reverse(); + } + + const paginationCursors = + hits.length > 0 + ? { + previousPageCursor: hits[0].sort, + nextPageCursor: hits[hits.length - 1].sort, + } + : undefined; + + const anomalies = hits.map((result) => { + const { + // eslint-disable-next-line @typescript-eslint/naming-convention + job_id, + record_score: anomalyScore, + typical, + actual, + bucket_span: duration, + timestamp: anomalyStartTime, + by_field_value: categoryId, + } = result._source; + + return { + id: result._id, + anomalyScore, + dataset: '', + typical: typical[0], + actual: actual[0], + jobId: job_id, + hostName: result._source['host.name'], + startTime: anomalyStartTime, + duration: duration * 1000, + categoryId, + }; + }); + + const fetchLogEntryAnomaliesSpan = finalizeFetchLogEntryAnomaliesSpan(); + + return { + anomalies, + paginationCursors, + hasMoreEntries, + timing: { + spans: [fetchLogEntryAnomaliesSpan], + }, + }; +} + +// TODO: FIgure out why we need datasets +export async function getMetricsHostsAnomaliesDatasets( + context: { + infra: { + mlSystem: MlSystem; + mlAnomalyDetectors: MlAnomalyDetectors; + spaceId: string; + }; + }, + sourceId: string, + startTime: number, + endTime: number +) { + const { + jobIds, + timing: { spans: jobSpans }, + } = await getCompatibleAnomaliesJobIds( + context.infra.spaceId, + sourceId, + context.infra.mlAnomalyDetectors + ); + + if (jobIds.length === 0) { + throw new InsufficientAnomalyMlJobsConfigured( + 'Log rate or categorisation ML jobs need to be configured to search for anomaly datasets' + ); + } + + const { + data: datasets, + timing: { spans: datasetsSpans }, + } = await getLogEntryDatasets(context.infra.mlSystem, startTime, endTime, jobIds); + + return { + datasets, + timing: { + spans: [...jobSpans, ...datasetsSpans], + }, + }; +} diff --git a/x-pack/plugins/infra/server/lib/infra_ml/metrics_k8s_anomalies.ts b/x-pack/plugins/infra/server/lib/infra_ml/metrics_k8s_anomalies.ts new file mode 100644 index 0000000000000..29507900e1847 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/infra_ml/metrics_k8s_anomalies.ts @@ -0,0 +1,272 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RequestHandlerContext } from 'src/core/server'; +import { InfraRequestHandlerContext } from '../../types'; +import { TracingSpan, startTracingSpan } from '../../../common/performance_tracing'; +import { fetchMlJob, getLogEntryDatasets } from './common'; +import { getJobId, metricsK8SJobTypes } from '../../../common/infra_ml'; +import { Sort, Pagination } from '../../../common/http_api/infra_ml'; +import type { MlSystem, MlAnomalyDetectors } from '../../types'; +import { InsufficientAnomalyMlJobsConfigured, isMlPrivilegesError } from './errors'; +import { decodeOrThrow } from '../../../common/runtime_types'; +import { + metricsK8sAnomaliesResponseRT, + createMetricsK8sAnomaliesQuery, +} from './queries/metrics_k8s_anomalies'; + +interface MappedAnomalyHit { + id: string; + anomalyScore: number; + // dataset: string; + typical: number; + actual: number; + jobId: string; + startTime: number; + duration: number; + categoryId?: string; +} + +async function getCompatibleAnomaliesJobIds( + spaceId: string, + sourceId: string, + mlAnomalyDetectors: MlAnomalyDetectors +) { + const metricsK8sJobIds = metricsK8SJobTypes.map((jt) => getJobId(spaceId, sourceId, jt)); + + const jobIds: string[] = []; + let jobSpans: TracingSpan[] = []; + + try { + await Promise.all( + metricsK8sJobIds.map((id) => { + return (async () => { + const { + timing: { spans }, + } = await fetchMlJob(mlAnomalyDetectors, id); + jobIds.push(id); + jobSpans = [...jobSpans, ...spans]; + })(); + }) + ); + } catch (e) { + if (isMlPrivilegesError(e)) { + throw e; + } + // An error is also thrown when no jobs are found + } + + return { + jobIds, + timing: { spans: jobSpans }, + }; +} + +export async function getMetricK8sAnomalies( + context: RequestHandlerContext & { infra: Required }, + sourceId: string, + startTime: number, + endTime: number, + sort: Sort, + pagination: Pagination +) { + const finalizeMetricsK8sAnomaliesSpan = startTracingSpan('get metrics k8s entry anomalies'); + + const { + jobIds, + timing: { spans: jobSpans }, + } = await getCompatibleAnomaliesJobIds( + context.infra.spaceId, + sourceId, + context.infra.mlAnomalyDetectors + ); + + if (jobIds.length === 0) { + throw new InsufficientAnomalyMlJobsConfigured( + 'Log rate or categorisation ML jobs need to be configured to search anomalies' + ); + } + + const { + anomalies, + paginationCursors, + hasMoreEntries, + timing: { spans: fetchLogEntryAnomaliesSpans }, + } = await fetchMetricK8sAnomalies( + context.infra.mlSystem, + jobIds, + startTime, + endTime, + sort, + pagination + ); + + const data = anomalies.map((anomaly) => { + const { jobId } = anomaly; + + return parseAnomalyResult(anomaly, jobId); + }); + + const metricsK8sAnomaliesSpan = finalizeMetricsK8sAnomaliesSpan(); + + return { + data, + paginationCursors, + hasMoreEntries, + timing: { + spans: [metricsK8sAnomaliesSpan, ...jobSpans, ...fetchLogEntryAnomaliesSpans], + }, + }; +} + +const parseAnomalyResult = (anomaly: MappedAnomalyHit, jobId: string) => { + const { + id, + anomalyScore, + // dataset, + typical, + actual, + duration, + startTime: anomalyStartTime, + } = anomaly; + + return { + id, + anomalyScore, + // dataset, + typical, + actual, + duration, + startTime: anomalyStartTime, + type: 'metrics_k8s' as const, + jobId, + }; +}; + +async function fetchMetricK8sAnomalies( + mlSystem: MlSystem, + jobIds: string[], + startTime: number, + endTime: number, + sort: Sort, + pagination: Pagination +) { + // We'll request 1 extra entry on top of our pageSize to determine if there are + // more entries to be fetched. This avoids scenarios where the client side can't + // determine if entries.length === pageSize actually means there are more entries / next page + // or not. + const expandedPagination = { ...pagination, pageSize: pagination.pageSize + 1 }; + + const finalizeFetchLogEntryAnomaliesSpan = startTracingSpan('fetch metrics k8s anomalies'); + + const results = decodeOrThrow(metricsK8sAnomaliesResponseRT)( + await mlSystem.mlAnomalySearch( + createMetricsK8sAnomaliesQuery(jobIds, startTime, endTime, sort, expandedPagination) + ) + ); + + const { + hits: { hits }, + } = results; + const hasMoreEntries = hits.length > pagination.pageSize; + + // An extra entry was found and hasMoreEntries has been determined, the extra entry can be removed. + if (hasMoreEntries) { + hits.pop(); + } + + // To "search_before" the sort order will have been reversed for ES. + // The results are now reversed back, to match the requested sort. + if (pagination.cursor && 'searchBefore' in pagination.cursor) { + hits.reverse(); + } + + const paginationCursors = + hits.length > 0 + ? { + previousPageCursor: hits[0].sort, + nextPageCursor: hits[hits.length - 1].sort, + } + : undefined; + + const anomalies = hits.map((result) => { + const { + // eslint-disable-next-line @typescript-eslint/naming-convention + job_id, + record_score: anomalyScore, + typical, + actual, + // partition_field_value: dataset, + bucket_span: duration, + timestamp: anomalyStartTime, + by_field_value: categoryId, + } = result._source; + + return { + id: result._id, + anomalyScore, + // dataset, + typical: typical[0], + actual: actual[0], + jobId: job_id, + startTime: anomalyStartTime, + duration: duration * 1000, + categoryId, + }; + }); + + const fetchLogEntryAnomaliesSpan = finalizeFetchLogEntryAnomaliesSpan(); + + return { + anomalies, + paginationCursors, + hasMoreEntries, + timing: { + spans: [fetchLogEntryAnomaliesSpan], + }, + }; +} + +// TODO: FIgure out why we need datasets +export async function getMetricK8sAnomaliesDatasets( + context: { + infra: { + mlSystem: MlSystem; + mlAnomalyDetectors: MlAnomalyDetectors; + spaceId: string; + }; + }, + sourceId: string, + startTime: number, + endTime: number +) { + const { + jobIds, + timing: { spans: jobSpans }, + } = await getCompatibleAnomaliesJobIds( + context.infra.spaceId, + sourceId, + context.infra.mlAnomalyDetectors + ); + + if (jobIds.length === 0) { + throw new InsufficientAnomalyMlJobsConfigured( + 'Log rate or categorisation ML jobs need to be configured to search for anomaly datasets' + ); + } + + const { + data: datasets, + timing: { spans: datasetsSpans }, + } = await getLogEntryDatasets(context.infra.mlSystem, startTime, endTime, jobIds); + + return { + datasets, + timing: { + spans: [...jobSpans, ...datasetsSpans], + }, + }; +} diff --git a/x-pack/plugins/infra/server/lib/infra_ml/queries/common.ts b/x-pack/plugins/infra/server/lib/infra_ml/queries/common.ts new file mode 100644 index 0000000000000..63e39ef022392 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/infra_ml/queries/common.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const defaultRequestParameters = { + allowNoIndices: true, + ignoreUnavailable: true, + trackScores: false, + trackTotalHits: false, +}; + +export const createJobIdFilters = (jobId: string) => [ + { + term: { + job_id: { + value: jobId, + }, + }, + }, +]; + +export const createJobIdsFilters = (jobIds: string[]) => [ + { + terms: { + job_id: jobIds, + }, + }, +]; + +export const createTimeRangeFilters = (startTime: number, endTime: number) => [ + { + range: { + timestamp: { + gte: startTime, + lte: endTime, + }, + }, + }, +]; + +export const createResultTypeFilters = (resultTypes: Array<'model_plot' | 'record'>) => [ + { + terms: { + result_type: resultTypes, + }, + }, +]; + +export const createCategoryIdFilters = (categoryIds: number[]) => [ + { + terms: { + category_id: categoryIds, + }, + }, +]; + +export const createDatasetsFilters = (datasets?: string[]) => + datasets && datasets.length > 0 + ? [ + { + terms: { + partition_field_value: datasets, + }, + }, + ] + : []; diff --git a/x-pack/plugins/infra/server/lib/infra_ml/queries/index.ts b/x-pack/plugins/infra/server/lib/infra_ml/queries/index.ts new file mode 100644 index 0000000000000..5a42011e1cea1 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/infra_ml/queries/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export * from './metrics_k8s_anomalies'; +export * from './metrics_hosts_anomalies'; diff --git a/x-pack/plugins/infra/server/lib/infra_ml/queries/log_entry_data_sets.ts b/x-pack/plugins/infra/server/lib/infra_ml/queries/log_entry_data_sets.ts new file mode 100644 index 0000000000000..53971a91d86b1 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/infra_ml/queries/log_entry_data_sets.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; +import { commonSearchSuccessResponseFieldsRT } from '../../../utils/elasticsearch_runtime_types'; +import { + createJobIdsFilters, + createResultTypeFilters, + createTimeRangeFilters, + defaultRequestParameters, +} from './common'; + +export const createLogEntryDatasetsQuery = ( + jobIds: string[], + startTime: number, + endTime: number, + size: number, + afterKey?: CompositeDatasetKey +) => ({ + ...defaultRequestParameters, + body: { + query: { + bool: { + filter: [ + ...createJobIdsFilters(jobIds), + ...createTimeRangeFilters(startTime, endTime), + ...createResultTypeFilters(['model_plot']), + ], + }, + }, + aggs: { + dataset_buckets: { + composite: { + after: afterKey, + size, + sources: [ + { + dataset: { + terms: { + field: 'partition_field_value', + order: 'asc', + }, + }, + }, + ], + }, + }, + }, + }, + size: 0, +}); + +const compositeDatasetKeyRT = rt.type({ + dataset: rt.string, +}); + +export type CompositeDatasetKey = rt.TypeOf; + +const logEntryDatasetBucketRT = rt.type({ + key: compositeDatasetKeyRT, +}); + +export type LogEntryDatasetBucket = rt.TypeOf; + +export const logEntryDatasetsResponseRT = rt.intersection([ + commonSearchSuccessResponseFieldsRT, + rt.partial({ + aggregations: rt.type({ + dataset_buckets: rt.intersection([ + rt.type({ + buckets: rt.array(logEntryDatasetBucketRT), + }), + rt.partial({ + after_key: compositeDatasetKeyRT, + }), + ]), + }), + }), +]); + +export type LogEntryDatasetsResponse = rt.TypeOf; diff --git a/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_hosts_anomalies.ts b/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_hosts_anomalies.ts new file mode 100644 index 0000000000000..b61119b60bc18 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_hosts_anomalies.ts @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; +import { commonSearchSuccessResponseFieldsRT } from '../../../utils/elasticsearch_runtime_types'; +import { + createJobIdsFilters, + createTimeRangeFilters, + createResultTypeFilters, + defaultRequestParameters, +} from './common'; +import { Sort, Pagination } from '../../../../common/http_api/infra_ml'; + +// TODO: Reassess validity of this against ML docs +const TIEBREAKER_FIELD = '_doc'; + +const sortToMlFieldMap = { + dataset: 'partition_field_value', + anomalyScore: 'record_score', + startTime: 'timestamp', +}; + +export const createMetricsHostsAnomaliesQuery = ( + jobIds: string[], + startTime: number, + endTime: number, + sort: Sort, + pagination: Pagination +) => { + const { field } = sort; + const { pageSize } = pagination; + + const filters = [ + ...createJobIdsFilters(jobIds), + ...createTimeRangeFilters(startTime, endTime), + ...createResultTypeFilters(['record']), + ]; + + const sourceFields = [ + 'job_id', + 'record_score', + 'typical', + 'actual', + 'partition_field_value', + 'timestamp', + 'bucket_span', + 'by_field_value', + 'host.name', + 'influencers.influencer_field_name', + 'influencers.influencer_field_values', + ]; + + const { querySortDirection, queryCursor } = parsePaginationCursor(sort, pagination); + + const sortOptions = [ + { [sortToMlFieldMap[field]]: querySortDirection }, + { [TIEBREAKER_FIELD]: querySortDirection }, // Tiebreaker + ]; + + const resultsQuery = { + ...defaultRequestParameters, + body: { + query: { + bool: { + filter: filters, + }, + }, + search_after: queryCursor, + sort: sortOptions, + size: pageSize, + _source: sourceFields, + }, + }; + + return resultsQuery; +}; + +export const metricsHostsAnomalyHitRT = rt.type({ + _id: rt.string, + _source: rt.intersection([ + rt.type({ + job_id: rt.string, + record_score: rt.number, + typical: rt.array(rt.number), + actual: rt.array(rt.number), + 'host.name': rt.array(rt.string), + bucket_span: rt.number, + timestamp: rt.number, + }), + rt.partial({ + by_field_value: rt.string, + }), + ]), + sort: rt.tuple([rt.union([rt.string, rt.number]), rt.union([rt.string, rt.number])]), +}); + +export type MetricsHostsAnomalyHit = rt.TypeOf; + +export const metricsHostsAnomaliesResponseRT = rt.intersection([ + commonSearchSuccessResponseFieldsRT, + rt.type({ + hits: rt.type({ + hits: rt.array(metricsHostsAnomalyHitRT), + }), + }), +]); + +export type MetricsHostsAnomaliesResponseRT = rt.TypeOf; + +const parsePaginationCursor = (sort: Sort, pagination: Pagination) => { + const { cursor } = pagination; + const { direction } = sort; + + if (!cursor) { + return { querySortDirection: direction, queryCursor: undefined }; + } + + // We will always use ES's search_after to paginate, to mimic "search_before" behaviour we + // need to reverse the user's chosen search direction for the ES query. + if ('searchBefore' in cursor) { + return { + querySortDirection: direction === 'desc' ? 'asc' : 'desc', + queryCursor: cursor.searchBefore, + }; + } else { + return { querySortDirection: direction, queryCursor: cursor.searchAfter }; + } +}; diff --git a/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_k8s_anomalies.ts b/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_k8s_anomalies.ts new file mode 100644 index 0000000000000..84ed8b064c5ca --- /dev/null +++ b/x-pack/plugins/infra/server/lib/infra_ml/queries/metrics_k8s_anomalies.ts @@ -0,0 +1,128 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; +import { commonSearchSuccessResponseFieldsRT } from '../../../utils/elasticsearch_runtime_types'; +import { + createJobIdsFilters, + createTimeRangeFilters, + createResultTypeFilters, + defaultRequestParameters, +} from './common'; +import { Sort, Pagination } from '../../../../common/http_api/infra_ml'; + +// TODO: Reassess validity of this against ML docs +const TIEBREAKER_FIELD = '_doc'; + +const sortToMlFieldMap = { + dataset: 'partition_field_value', + anomalyScore: 'record_score', + startTime: 'timestamp', +}; + +export const createMetricsK8sAnomaliesQuery = ( + jobIds: string[], + startTime: number, + endTime: number, + sort: Sort, + pagination: Pagination +) => { + const { field } = sort; + const { pageSize } = pagination; + + const filters = [ + ...createJobIdsFilters(jobIds), + ...createTimeRangeFilters(startTime, endTime), + ...createResultTypeFilters(['record']), + ]; + + const sourceFields = [ + 'job_id', + 'record_score', + 'typical', + 'actual', + 'partition_field_value', + 'timestamp', + 'bucket_span', + 'by_field_value', + ]; + + const { querySortDirection, queryCursor } = parsePaginationCursor(sort, pagination); + + const sortOptions = [ + { [sortToMlFieldMap[field]]: querySortDirection }, + { [TIEBREAKER_FIELD]: querySortDirection }, // Tiebreaker + ]; + + const resultsQuery = { + ...defaultRequestParameters, + body: { + query: { + bool: { + filter: filters, + }, + }, + search_after: queryCursor, + sort: sortOptions, + size: pageSize, + _source: sourceFields, + }, + }; + + return resultsQuery; +}; + +export const metricsK8sAnomalyHitRT = rt.type({ + _id: rt.string, + _source: rt.intersection([ + rt.type({ + job_id: rt.string, + record_score: rt.number, + typical: rt.array(rt.number), + actual: rt.array(rt.number), + // partition_field_value: rt.string, + bucket_span: rt.number, + timestamp: rt.number, + }), + rt.partial({ + by_field_value: rt.string, + }), + ]), + sort: rt.tuple([rt.union([rt.string, rt.number]), rt.union([rt.string, rt.number])]), +}); + +export type MetricsK8sAnomalyHit = rt.TypeOf; + +export const metricsK8sAnomaliesResponseRT = rt.intersection([ + commonSearchSuccessResponseFieldsRT, + rt.type({ + hits: rt.type({ + hits: rt.array(metricsK8sAnomalyHitRT), + }), + }), +]); + +export type MetricsK8sAnomaliesResponseRT = rt.TypeOf; + +const parsePaginationCursor = (sort: Sort, pagination: Pagination) => { + const { cursor } = pagination; + const { direction } = sort; + + if (!cursor) { + return { querySortDirection: direction, queryCursor: undefined }; + } + + // We will always use ES's search_after to paginate, to mimic "search_before" behaviour we + // need to reverse the user's chosen search direction for the ES query. + if ('searchBefore' in cursor) { + return { + querySortDirection: direction === 'desc' ? 'asc' : 'desc', + queryCursor: cursor.searchBefore, + }; + } else { + return { querySortDirection: direction, queryCursor: cursor.searchAfter }; + } +}; diff --git a/x-pack/plugins/infra/server/lib/infra_ml/queries/ml_jobs.ts b/x-pack/plugins/infra/server/lib/infra_ml/queries/ml_jobs.ts new file mode 100644 index 0000000000000..ee4ccbfaeb5a7 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/infra_ml/queries/ml_jobs.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; + +export const createMlJobsQuery = (jobIds: string[]) => ({ + method: 'GET', + path: `/_ml/anomaly_detectors/${jobIds.join(',')}`, + query: { + allow_no_jobs: true, + }, +}); + +export const mlJobRT = rt.type({ + job_id: rt.string, + custom_settings: rt.unknown, +}); + +export const mlJobsResponseRT = rt.type({ + jobs: rt.array(mlJobRT), +}); diff --git a/x-pack/plugins/infra/server/lib/log_analysis/queries/log_entry_examples.ts b/x-pack/plugins/infra/server/lib/log_analysis/queries/log_entry_examples.ts index eac5fa84d85a7..1b6a4c611e177 100644 --- a/x-pack/plugins/infra/server/lib/log_analysis/queries/log_entry_examples.ts +++ b/x-pack/plugins/infra/server/lib/log_analysis/queries/log_entry_examples.ts @@ -33,7 +33,7 @@ export const createLogEntryExamplesQuery = ( }, }, }, - ...(!!dataset + ...(dataset !== '' ? [ { term: { @@ -41,7 +41,19 @@ export const createLogEntryExamplesQuery = ( }, }, ] - : []), + : [ + { + bool: { + must_not: [ + { + exists: { + field: partitionField, + }, + }, + ], + }, + }, + ]), ...(categoryQuery ? [ { diff --git a/x-pack/legacy/server/lib/check_license/index.js b/x-pack/plugins/infra/server/routes/infra_ml/index.ts similarity index 83% rename from x-pack/legacy/server/lib/check_license/index.js rename to x-pack/plugins/infra/server/routes/infra_ml/index.ts index f2c070fd44b6e..38684cb22e237 100644 --- a/x-pack/legacy/server/lib/check_license/index.js +++ b/x-pack/plugins/infra/server/routes/infra_ml/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { checkLicense } from './check_license'; +export * from './results'; diff --git a/x-pack/legacy/server/lib/constants/index.ts b/x-pack/plugins/infra/server/routes/infra_ml/results/index.ts similarity index 74% rename from x-pack/legacy/server/lib/constants/index.ts rename to x-pack/plugins/infra/server/routes/infra_ml/results/index.ts index 2378aca824042..82e30291faa20 100644 --- a/x-pack/legacy/server/lib/constants/index.ts +++ b/x-pack/plugins/infra/server/routes/infra_ml/results/index.ts @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { XPACK_INFO_API_DEFAULT_POLL_FREQUENCY_IN_MILLIS } from './xpack_info'; +export * from './metrics_hosts_anomalies'; +export * from './metrics_k8s_anomalies'; diff --git a/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_hosts_anomalies.ts b/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_hosts_anomalies.ts new file mode 100644 index 0000000000000..29122ae159cdc --- /dev/null +++ b/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_hosts_anomalies.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from 'boom'; +import { InfraBackendLibs } from '../../../lib/infra_types'; +import { + INFA_ML_GET_METRICS_HOSTS_ANOMALIES_PATH, + getMetricsHostsAnomaliesSuccessReponsePayloadRT, + getMetricsHostsAnomaliesRequestPayloadRT, + GetMetricsHostsAnomaliesRequestPayload, + Sort, + Pagination, +} from '../../../../common/http_api/infra_ml'; +import { createValidationFunction } from '../../../../common/runtime_types'; +import { assertHasInfraMlPlugins } from '../../../utils/request_context'; + +import { isMlPrivilegesError } from '../../../lib/infra_ml/errors'; +import { getMetricsHostsAnomalies } from '../../../lib/infra_ml'; + +export const initGetHostsAnomaliesRoute = ({ framework }: InfraBackendLibs) => { + framework.registerRoute( + { + method: 'post', + path: INFA_ML_GET_METRICS_HOSTS_ANOMALIES_PATH, + validate: { + body: createValidationFunction(getMetricsHostsAnomaliesRequestPayloadRT), + }, + }, + framework.router.handleLegacyErrors(async (requestContext, request, response) => { + const { + data: { + sourceId, + timeRange: { startTime, endTime }, + sort: sortParam, + pagination: paginationParam, + }, + } = request.body; + + const { sort, pagination } = getSortAndPagination(sortParam, paginationParam); + + try { + assertHasInfraMlPlugins(requestContext); + + const { + data: anomalies, + paginationCursors, + hasMoreEntries, + timing, + } = await getMetricsHostsAnomalies( + requestContext, + sourceId, + startTime, + endTime, + sort, + pagination + ); + + // console.log('---- anomalies', anomalies); + + return response.ok({ + body: getMetricsHostsAnomaliesSuccessReponsePayloadRT.encode({ + data: { + anomalies, + hasMoreEntries, + paginationCursors, + }, + timing, + }), + }); + } catch (error) { + if (Boom.isBoom(error)) { + throw error; + } + + if (isMlPrivilegesError(error)) { + return response.customError({ + statusCode: 403, + body: { + message: error.message, + }, + }); + } + + return response.customError({ + statusCode: error.statusCode ?? 500, + body: { + message: error.message ?? 'An unexpected error occurred', + }, + }); + } + }) + ); +}; + +const getSortAndPagination = ( + sort: Partial = {}, + pagination: Partial = {} +): { + sort: Sort; + pagination: Pagination; +} => { + const sortDefaults = { + field: 'anomalyScore' as const, + direction: 'desc' as const, + }; + + const sortWithDefaults = { + ...sortDefaults, + ...sort, + }; + + const paginationDefaults = { + pageSize: 50, + }; + + const paginationWithDefaults = { + ...paginationDefaults, + ...pagination, + }; + + return { sort: sortWithDefaults, pagination: paginationWithDefaults }; +}; diff --git a/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_k8s_anomalies.ts b/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_k8s_anomalies.ts new file mode 100644 index 0000000000000..5260c55836c59 --- /dev/null +++ b/x-pack/plugins/infra/server/routes/infra_ml/results/metrics_k8s_anomalies.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from 'boom'; +import { InfraBackendLibs } from '../../../lib/infra_types'; +import { + INFA_ML_GET_METRICS_K8S_ANOMALIES_PATH, + getMetricsK8sAnomaliesSuccessReponsePayloadRT, + getMetricsK8sAnomaliesRequestPayloadRT, + GetMetricsK8sAnomaliesRequestPayload, + Sort, + Pagination, +} from '../../../../common/http_api/infra_ml'; +import { createValidationFunction } from '../../../../common/runtime_types'; +import { assertHasInfraMlPlugins } from '../../../utils/request_context'; +import { getMetricK8sAnomalies } from '../../../lib/infra_ml'; +import { isMlPrivilegesError } from '../../../lib/infra_ml/errors'; + +export const initGetK8sAnomaliesRoute = ({ framework }: InfraBackendLibs) => { + framework.registerRoute( + { + method: 'post', + path: INFA_ML_GET_METRICS_K8S_ANOMALIES_PATH, + validate: { + body: createValidationFunction(getMetricsK8sAnomaliesRequestPayloadRT), + }, + }, + framework.router.handleLegacyErrors(async (requestContext, request, response) => { + const { + data: { + sourceId, + timeRange: { startTime, endTime }, + sort: sortParam, + pagination: paginationParam, + }, + } = request.body; + + const { sort, pagination } = getSortAndPagination(sortParam, paginationParam); + + try { + assertHasInfraMlPlugins(requestContext); + + const { + data: anomalies, + paginationCursors, + hasMoreEntries, + timing, + } = await getMetricK8sAnomalies( + requestContext, + sourceId, + startTime, + endTime, + sort, + pagination + ); + + return response.ok({ + body: getMetricsK8sAnomaliesSuccessReponsePayloadRT.encode({ + data: { + anomalies, + hasMoreEntries, + paginationCursors, + }, + timing, + }), + }); + } catch (error) { + if (Boom.isBoom(error)) { + throw error; + } + + if (isMlPrivilegesError(error)) { + return response.customError({ + statusCode: 403, + body: { + message: error.message, + }, + }); + } + + return response.customError({ + statusCode: error.statusCode ?? 500, + body: { + message: error.message ?? 'An unexpected error occurred', + }, + }); + } + }) + ); +}; + +const getSortAndPagination = ( + sort: Partial = {}, + pagination: Partial = {} +): { + sort: Sort; + pagination: Pagination; +} => { + const sortDefaults = { + field: 'anomalyScore' as const, + direction: 'desc' as const, + }; + + const sortWithDefaults = { + ...sortDefaults, + ...sort, + }; + + const paginationDefaults = { + pageSize: 50, + }; + + const paginationWithDefaults = { + ...paginationDefaults, + ...pagination, + }; + + return { sort: sortWithDefaults, pagination: paginationWithDefaults }; +}; diff --git a/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts b/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts index 700f4ef39bb66..b18b45f4935d2 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { findInventoryFields } from '../../../../common/inventory_models'; +import { findInventoryFields, findInventoryModel } from '../../../../common/inventory_models'; import { MetricsAPIRequest, SnapshotRequest } from '../../../../common/http_api'; import { ESSearchClient } from '../../../lib/metrics/types'; import { InfraSource } from '../../../lib/sources'; @@ -34,7 +34,7 @@ export const transformRequestToMetricsAPIRequest = async ( interval: timeRangeWithIntervalApplied.interval, }, metrics: transformSnapshotMetricsToMetricsAPIMetrics(snapshotRequest), - limit: snapshotRequest.overrideCompositeSize ? snapshotRequest.overrideCompositeSize : 10, + limit: snapshotRequest.overrideCompositeSize ? snapshotRequest.overrideCompositeSize : 5, alignDataToEnd: true, }; @@ -52,12 +52,19 @@ export const transformRequestToMetricsAPIRequest = async ( filters.push({ term: { 'cloud.region': snapshotRequest.region } }); } + const inventoryModel = findInventoryModel(snapshotRequest.nodeType); + if (inventoryModel && inventoryModel.nodeFilter) { + inventoryModel.nodeFilter?.forEach((f) => filters.push(f)); + } + const inventoryFields = findInventoryFields( snapshotRequest.nodeType, source.configuration.fields ); - const groupBy = snapshotRequest.groupBy.map((g) => g.field).filter(Boolean) as string[]; - metricsApiRequest.groupBy = [...groupBy, inventoryFields.id]; + if (snapshotRequest.groupBy) { + const groupBy = snapshotRequest.groupBy.map((g) => g.field).filter(Boolean) as string[]; + metricsApiRequest.groupBy = [...groupBy, inventoryFields.id]; + } const metaAggregation = { id: META_KEY, diff --git a/x-pack/plugins/ingest_manager/common/constants/routes.ts b/x-pack/plugins/ingest_manager/common/constants/routes.ts index 3e065142ea101..69672dfb9ec6c 100644 --- a/x-pack/plugins/ingest_manager/common/constants/routes.ts +++ b/x-pack/plugins/ingest_manager/common/constants/routes.ts @@ -15,9 +15,11 @@ export const LIMITED_CONCURRENCY_ROUTE_TAG = 'ingest:limited-concurrency'; // EPM API routes const EPM_PACKAGES_MANY = `${EPM_API_ROOT}/packages`; +const EPM_PACKAGES_BULK = `${EPM_PACKAGES_MANY}/_bulk`; const EPM_PACKAGES_ONE = `${EPM_PACKAGES_MANY}/{pkgkey}`; const EPM_PACKAGES_FILE = `${EPM_PACKAGES_MANY}/{pkgName}/{pkgVersion}`; export const EPM_API_ROUTES = { + BULK_INSTALL_PATTERN: EPM_PACKAGES_BULK, LIST_PATTERN: EPM_PACKAGES_MANY, LIMITED_LIST_PATTERN: `${EPM_PACKAGES_MANY}/limited`, INFO_PATTERN: EPM_PACKAGES_ONE, @@ -84,8 +86,11 @@ export const AGENT_API_ROUTES = { ACTIONS_PATTERN: `${FLEET_API_ROOT}/agents/{agentId}/actions`, ENROLL_PATTERN: `${FLEET_API_ROOT}/agents/enroll`, UNENROLL_PATTERN: `${FLEET_API_ROOT}/agents/{agentId}/unenroll`, + BULK_UNENROLL_PATTERN: `${FLEET_API_ROOT}/agents/bulk_unenroll`, REASSIGN_PATTERN: `${FLEET_API_ROOT}/agents/{agentId}/reassign`, + BULK_REASSIGN_PATTERN: `${FLEET_API_ROOT}/agents/bulk_reassign`, STATUS_PATTERN: `${FLEET_API_ROOT}/agent-status`, + UPGRADE_PATTERN: `${FLEET_API_ROOT}/agents/{agentId}/upgrade`, }; export const ENROLLMENT_API_KEY_ROUTES = { diff --git a/x-pack/plugins/ingest_manager/common/services/agent_status.ts b/x-pack/plugins/ingest_manager/common/services/agent_status.ts index fe4e094e1bb22..70f4d7f9344f9 100644 --- a/x-pack/plugins/ingest_manager/common/services/agent_status.ts +++ b/x-pack/plugins/ingest_manager/common/services/agent_status.ts @@ -19,6 +19,9 @@ export function getAgentStatus(agent: Agent, now: number = Date.now()): AgentSta if (!agent.last_checkin) { return 'enrolling'; } + if (agent.upgrade_started_at && !agent.upgraded_at) { + return 'upgrading'; + } const msLastCheckIn = new Date(lastCheckIn || 0).getTime(); const msSinceLastCheckIn = new Date().getTime() - msLastCheckIn; diff --git a/x-pack/plugins/ingest_manager/common/services/index.ts b/x-pack/plugins/ingest_manager/common/services/index.ts index 46a1c65872d1b..4bffa01ad5ee2 100644 --- a/x-pack/plugins/ingest_manager/common/services/index.ts +++ b/x-pack/plugins/ingest_manager/common/services/index.ts @@ -12,3 +12,4 @@ export { isPackageLimited, doesAgentPolicyAlreadyIncludePackage } from './limite export { decodeCloudId } from './decode_cloud_id'; export { isValidNamespace } from './is_valid_namespace'; export { isDiffPathProtocol } from './is_diff_path_protocol'; +export { LicenseService } from './license'; diff --git a/x-pack/plugins/ingest_manager/common/services/license.ts b/x-pack/plugins/ingest_manager/common/services/license.ts new file mode 100644 index 0000000000000..6d9b20a8456c0 --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/services/license.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Observable, Subscription } from 'rxjs'; +import { ILicense } from '../../../licensing/common/types'; + +// Generic license service class that works with the license observable +// Both server and client plugins instancates a singleton version of this class +export class LicenseService { + private observable: Observable | null = null; + private subscription: Subscription | null = null; + private licenseInformation: ILicense | null = null; + + private updateInformation(licenseInformation: ILicense) { + this.licenseInformation = licenseInformation; + } + + public start(license$: Observable) { + this.observable = license$; + this.subscription = this.observable.subscribe(this.updateInformation.bind(this)); + } + + public stop() { + if (this.subscription) { + this.subscription.unsubscribe(); + } + } + + public getLicenseInformation() { + return this.licenseInformation; + } + + public getLicenseInformation$() { + return this.observable; + } + + public isGoldPlus() { + return ( + this.licenseInformation?.isAvailable && + this.licenseInformation?.isActive && + this.licenseInformation?.hasAtLeast('gold') + ); + } +} diff --git a/x-pack/plugins/ingest_manager/common/services/routes.ts b/x-pack/plugins/ingest_manager/common/services/routes.ts index b7521f95b4f83..3c3534926908a 100644 --- a/x-pack/plugins/ingest_manager/common/services/routes.ts +++ b/x-pack/plugins/ingest_manager/common/services/routes.ts @@ -46,6 +46,10 @@ export const epmRouteService = { ); // trim trailing slash }, + getBulkInstallPath: () => { + return EPM_API_ROUTES.BULK_INSTALL_PATTERN; + }, + getRemovePath: (pkgkey: string) => { return EPM_API_ROUTES.DELETE_PATTERN.replace('{pkgkey}', pkgkey).replace(/\/$/, ''); // trim trailing slash }, @@ -127,8 +131,10 @@ export const agentRouteService = { getEventsPath: (agentId: string) => AGENT_API_ROUTES.EVENTS_PATTERN.replace('{agentId}', agentId), getUnenrollPath: (agentId: string) => AGENT_API_ROUTES.UNENROLL_PATTERN.replace('{agentId}', agentId), + getBulkUnenrollPath: () => AGENT_API_ROUTES.BULK_UNENROLL_PATTERN, getReassignPath: (agentId: string) => AGENT_API_ROUTES.REASSIGN_PATTERN.replace('{agentId}', agentId), + getBulkReassignPath: () => AGENT_API_ROUTES.BULK_REASSIGN_PATTERN, getListPath: () => AGENT_API_ROUTES.LIST_PATTERN, getStatusPath: () => AGENT_API_ROUTES.STATUS_PATTERN, }; diff --git a/x-pack/plugins/ingest_manager/common/types/models/agent.ts b/x-pack/plugins/ingest_manager/common/types/models/agent.ts index a204373fe2e56..7110fd4ce52ea 100644 --- a/x-pack/plugins/ingest_manager/common/types/models/agent.ts +++ b/x-pack/plugins/ingest_manager/common/types/models/agent.ts @@ -19,10 +19,10 @@ export type AgentStatus = | 'warning' | 'enrolling' | 'unenrolling' + | 'upgrading' | 'degraded'; -export type AgentActionType = 'CONFIG_CHANGE' | 'UNENROLL'; - +export type AgentActionType = 'CONFIG_CHANGE' | 'UNENROLL' | 'UPGRADE'; export interface NewAgentAction { type: AgentActionType; data?: any; @@ -65,7 +65,6 @@ export type AgentPolicyActionSOAttributes = CommonAgentActionSOAttributes & { policy_id: string; policy_revision: number; }; - export type BaseAgentActionSOAttributes = AgentActionSOAttributes | AgentPolicyActionSOAttributes; export interface NewAgentEvent { @@ -110,6 +109,8 @@ interface AgentBase { enrolled_at: string; unenrolled_at?: string; unenrollment_started_at?: string; + upgraded_at?: string; + upgrade_started_at?: string; shared_id?: string; access_api_key_id?: string; default_api_key?: string; diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/agent.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/agent.ts index 54cdeade3764e..ab4c372c4e1d6 100644 --- a/x-pack/plugins/ingest_manager/common/types/rest_spec/agent.ts +++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/agent.ts @@ -26,6 +26,7 @@ export interface GetAgentsRequest { export interface GetAgentsResponse { list: Agent[]; total: number; + totalInactive: number; page: number; perPage: number; } @@ -104,11 +105,31 @@ export interface PostAgentUnenrollRequest { params: { agentId: string; }; + body: { + force?: boolean; + }; } // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface PostAgentUnenrollResponse {} +export interface PostAgentUpgradeRequest { + params: { + agentId: string; + }; +} +export interface PostBulkAgentUnenrollRequest { + body: { + agents: string[] | string; + force?: boolean; + }; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface PostAgentUpgradeResponse {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface PostBulkAgentUnenrollResponse {} + export interface PutAgentReassignRequest { params: { agentId: string; @@ -119,6 +140,20 @@ export interface PutAgentReassignRequest { // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface PutAgentReassignResponse {} +export interface PostBulkAgentReassignRequest { + body: { + policy_id: string; + agents: string[] | string; + }; +} + +export interface PostBulkAgentReassignResponse { + [key: string]: { + success: boolean; + error?: Error; + }; +} + export interface GetOneAgentEventsRequest { params: { agentId: string; diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/epm.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/epm.ts index 54e767fee4b22..7ed2fed91aa93 100644 --- a/x-pack/plugins/ingest_manager/common/types/rest_spec/epm.ts +++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/epm.ts @@ -71,6 +71,30 @@ export interface InstallPackageResponse { response: AssetReference[]; } +export interface IBulkInstallPackageError { + name: string; + statusCode: number; + error: string | Error; +} + +export interface BulkInstallPackageInfo { + name: string; + newVersion: string; + // this will be null if no package was present before the upgrade (aka it was an install) + oldVersion: string | null; + assets: AssetReference[]; +} + +export interface BulkInstallPackagesResponse { + response: Array; +} + +export interface BulkInstallPackagesRequest { + body: { + packages: string[]; + }; +} + export interface MessageResponse { response: string; } diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts index 36b7d412bf276..64434e163f043 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/index.ts @@ -8,6 +8,7 @@ export { useCapabilities } from './use_capabilities'; export { useCore } from './use_core'; export { useConfig, ConfigContext } from './use_config'; export { useSetupDeps, useStartDeps, DepsContext } from './use_deps'; +export { licenseService, useLicense } from './use_license'; export { useBreadcrumbs } from './use_breadcrumbs'; export { useLink } from './use_link'; export { useKibanaLink } from './use_kibana_link'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_license.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_license.ts new file mode 100644 index 0000000000000..411a6d6f2168f --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_license.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { LicenseService } from '../services'; + +export const licenseService = new LicenseService(); + +export function useLicense() { + return licenseService; +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agents.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agents.ts index cad1791af41be..41967fd068e0b 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agents.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agents.ts @@ -10,8 +10,14 @@ import { GetOneAgentResponse, GetOneAgentEventsResponse, GetOneAgentEventsRequest, + PostAgentUnenrollRequest, + PostBulkAgentUnenrollRequest, + PostBulkAgentUnenrollResponse, + PostAgentUnenrollResponse, PutAgentReassignRequest, PutAgentReassignResponse, + PostBulkAgentReassignRequest, + PostBulkAgentReassignResponse, GetAgentsRequest, GetAgentsResponse, GetAgentStatusRequest, @@ -83,3 +89,40 @@ export function sendPutAgentReassign( ...options, }); } + +export function sendPostBulkAgentReassign( + body: PostBulkAgentReassignRequest['body'], + options?: RequestOptions +) { + return sendRequest({ + method: 'post', + path: agentRouteService.getBulkReassignPath(), + body, + ...options, + }); +} + +export function sendPostAgentUnenroll( + agentId: string, + body: PostAgentUnenrollRequest['body'], + options?: RequestOptions +) { + return sendRequest({ + path: agentRouteService.getUnenrollPath(agentId), + method: 'post', + body, + ...options, + }); +} + +export function sendPostBulkAgentUnenroll( + body: PostBulkAgentUnenrollRequest['body'], + options?: RequestOptions +) { + return sendRequest({ + path: agentRouteService.getBulkUnenrollPath(), + method: 'post', + body, + ...options, + }); +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx index 5520a50463db4..0bef3c20ddd1a 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx @@ -12,7 +12,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import styled from 'styled-components'; import { EuiErrorBoundary, EuiPanel, EuiEmptyPrompt, EuiCode } from '@elastic/eui'; import { CoreStart, AppMountParameters } from 'src/core/public'; -import { EuiThemeProvider } from '../../../../../legacy/common/eui_styled_components'; +import { EuiThemeProvider } from '../../../../xpack_legacy/common'; import { IngestManagerSetupDeps, IngestManagerConfigType, @@ -22,9 +22,16 @@ import { PAGE_ROUTING_PATHS } from './constants'; import { DefaultLayout, WithoutHeaderLayout } from './layouts'; import { Loading, Error } from './components'; import { IngestManagerOverview, EPMApp, AgentPolicyApp, FleetApp, DataStreamApp } from './sections'; -import { DepsContext, ConfigContext, useConfig } from './hooks'; +import { + DepsContext, + ConfigContext, + useConfig, + useCore, + sendSetup, + sendGetPermissionsCheck, + licenseService, +} from './hooks'; import { PackageInstallProvider } from './sections/epm/hooks'; -import { useCore, sendSetup, sendGetPermissionsCheck } from './hooks'; import { FleetStatusProvider } from './hooks/use_fleet_status'; import './index.scss'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; @@ -279,4 +286,5 @@ export function renderApp( export const teardownIngestManager = (coreStart: CoreStart) => { coreStart.chrome.docTitle.reset(); coreStart.chrome.setBreadcrumbs([]); + licenseService.stop(); }; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx index 636ff7a5ff989..ea5dcce8c05bb 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx @@ -9,7 +9,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { Agent } from '../../../../types'; import { useCapabilities } from '../../../../hooks'; import { ContextMenuActions } from '../../../../components'; -import { AgentUnenrollProvider, AgentReassignAgentPolicyFlyout } from '../../components'; +import { AgentUnenrollAgentModal, AgentReassignAgentPolicyFlyout } from '../../components'; import { useAgentRefresh } from '../hooks'; export const AgentDetailsActionMenu: React.FunctionComponent<{ @@ -20,6 +20,7 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{ const hasWriteCapabilites = useCapabilities().write; const refreshAgent = useAgentRefresh(); const [isReassignFlyoutOpen, setIsReassignFlyoutOpen] = useState(assignFlyoutOpenByDefault); + const [isUnenrollModalOpen, setIsUnenrollModalOpen] = useState(false); const isUnenrolling = agent.status === 'unenrolling'; const onClose = useMemo(() => { @@ -34,7 +35,20 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{ <> {isReassignFlyoutOpen && ( - + + + )} + {isUnenrollModalOpen && ( + + { + setIsUnenrollModalOpen(false); + refreshAgent(); + }} + useForceUnenroll={isUnenrolling} + /> )} , - - {(unenrollAgentsPrompt) => ( - { - unenrollAgentsPrompt([agent.id], 1, refreshAgent); - }} - > - {isUnenrolling ? ( - - ) : ( - - )} - + { + setIsUnenrollModalOpen(true); + }} + > + {isUnenrolling ? ( + + ) : ( + )} - , + , ]} /> diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/components/bulk_actions.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/components/bulk_actions.tsx new file mode 100644 index 0000000000000..25684c9faf594 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/components/bulk_actions.tsx @@ -0,0 +1,225 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { useState } from 'react'; +import styled from 'styled-components'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiPopover, + EuiContextMenu, + EuiButtonEmpty, + EuiIcon, + EuiPortal, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { Agent } from '../../../../types'; +import { AgentReassignAgentPolicyFlyout, AgentUnenrollAgentModal } from '../../components'; + +const Divider = styled.div` + width: 0; + height: ${(props) => props.theme.eui.euiSizeL}; + border-left: ${(props) => props.theme.eui.euiBorderThin}; +`; + +const FlexItem = styled(EuiFlexItem)` + height: ${(props) => props.theme.eui.euiSizeL}; +`; + +const Button = styled(EuiButtonEmpty)` + .euiButtonEmpty__text { + font-size: ${(props) => props.theme.eui.euiFontSizeXS}; + } +`; + +export type SelectionMode = 'manual' | 'query'; + +export const AgentBulkActions: React.FunctionComponent<{ + totalAgents: number; + totalInactiveAgents: number; + selectableAgents: number; + selectionMode: SelectionMode; + setSelectionMode: (mode: SelectionMode) => void; + currentQuery: string; + selectedAgents: Agent[]; + setSelectedAgents: (agents: Agent[]) => void; + refreshAgents: () => void; +}> = ({ + totalAgents, + totalInactiveAgents, + selectableAgents, + selectionMode, + setSelectionMode, + currentQuery, + selectedAgents, + setSelectedAgents, + refreshAgents, +}) => { + // Bulk actions menu states + const [isMenuOpen, setIsMenuOpen] = useState(false); + const closeMenu = () => setIsMenuOpen(false); + const openMenu = () => setIsMenuOpen(true); + + // Actions states + const [isReassignFlyoutOpen, setIsReassignFlyoutOpen] = useState(false); + const [isUnenrollModalOpen, setIsUnenrollModalOpen] = useState(false); + + // Check if user is working with only inactive agents + const atLeastOneActiveAgentSelected = + selectionMode === 'manual' + ? !!selectedAgents.find((agent) => agent.active) + : totalAgents > totalInactiveAgents; + + const panels = [ + { + id: 0, + items: [ + { + name: ( + + ), + icon: , + disabled: !atLeastOneActiveAgentSelected, + onClick: () => { + closeMenu(); + setIsReassignFlyoutOpen(true); + }, + }, + { + name: ( + + ), + icon: , + disabled: !atLeastOneActiveAgentSelected, + onClick: () => { + closeMenu(); + setIsUnenrollModalOpen(true); + }, + }, + { + name: ( + + ), + icon: , + onClick: () => { + closeMenu(); + setSelectionMode('manual'); + setSelectedAgents([]); + }, + }, + ], + }, + ]; + + return ( + <> + {isReassignFlyoutOpen && ( + + { + setIsReassignFlyoutOpen(false); + refreshAgents(); + }} + /> + + )} + {isUnenrollModalOpen && ( + + { + setIsUnenrollModalOpen(false); + refreshAgents(); + }} + /> + + )} + + + + + + + {(selectionMode === 'manual' && selectedAgents.length) || + (selectionMode === 'query' && totalAgents > 0) ? ( + <> + + + + + + + + } + isOpen={isMenuOpen} + closePopover={closeMenu} + panelPaddingSize="none" + anchorPosition="downLeft" + > + + + + {selectionMode === 'manual' && + selectedAgents.length === selectableAgents && + selectableAgents < totalAgents ? ( + + + + ) : null} + + ) : ( + + )} + + + ); +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx index 46f7ffb85b21f..0bc463ce98590 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { useState, useMemo, useCallback } from 'react'; +import React, { useState, useMemo, useCallback, useRef } from 'react'; import { EuiBasicTable, EuiButton, @@ -20,6 +20,7 @@ import { EuiContextMenuItem, EuiIcon, EuiPortal, + EuiHorizontalRule, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, FormattedRelative } from '@kbn/i18n/react'; @@ -33,11 +34,17 @@ import { useUrlParams, useLink, useBreadcrumbs, + useLicense, } from '../../../hooks'; import { SearchBar, ContextMenuActions } from '../../../components'; import { AgentStatusKueryHelper } from '../../../services'; import { AGENT_SAVED_OBJECT_TYPE } from '../../../constants'; -import { AgentReassignAgentPolicyFlyout, AgentHealth, AgentUnenrollProvider } from '../components'; +import { + AgentReassignAgentPolicyFlyout, + AgentHealth, + AgentUnenrollAgentModal, +} from '../components'; +import { AgentBulkActions, SelectionMode } from './components/bulk_actions'; const REFRESH_INTERVAL_MS = 5000; @@ -63,72 +70,68 @@ const statusFilters = [ }, ] as Array<{ label: string; status: string }>; -const RowActions = React.memo<{ agent: Agent; onReassignClick: () => void; refresh: () => void }>( - ({ agent, refresh, onReassignClick }) => { - const { getHref } = useLink(); - const hasWriteCapabilites = useCapabilities().write; +const RowActions = React.memo<{ + agent: Agent; + refresh: () => void; + onReassignClick: () => void; + onUnenrollClick: () => void; +}>(({ agent, refresh, onReassignClick, onUnenrollClick }) => { + const { getHref } = useLink(); + const hasWriteCapabilites = useCapabilities().write; - const isUnenrolling = agent.status === 'unenrolling'; - const [isMenuOpen, setIsMenuOpen] = useState(false); - return ( - setIsMenuOpen(isOpen)} - items={[ - + const isUnenrolling = agent.status === 'unenrolling'; + const [isMenuOpen, setIsMenuOpen] = useState(false); + return ( + setIsMenuOpen(isOpen)} + items={[ + + + , + { + onReassignClick(); + }} + disabled={!agent.active} + key="reassignPolicy" + > + + , + { + onUnenrollClick(); + }} + > + {isUnenrolling ? ( - , - { - onReassignClick(); - }} - disabled={!agent.active} - key="reassignPolicy" - > + ) : ( - , - - {(unenrollAgentsPrompt) => ( - { - unenrollAgentsPrompt([agent.id], 1, () => { - refresh(); - setIsMenuOpen(false); - }); - }} - > - {isUnenrolling ? ( - - ) : ( - - )} - - )} - , - ]} - /> - ); - } -); + )} + , + ]} + /> + ); +}); function safeMetadata(val: any) { if (typeof val !== 'string') { @@ -142,12 +145,16 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { const { getHref } = useLink(); const defaultKuery: string = (useUrlParams().urlParams.kuery as string) || ''; const hasWriteCapabilites = useCapabilities().write; + const isGoldPlus = useLicense().isGoldPlus(); // Agent data states const [showInactive, setShowInactive] = useState(false); // Table and search states - const [search, setSearch] = useState(defaultKuery); + const [search, setSearch] = useState(defaultKuery); + const [selectionMode, setSelectionMode] = useState('manual'); + const [selectedAgents, setSelectedAgents] = useState([]); + const tableRef = useRef>(null); const { pagination, pageSizeOptions, setPagination } = usePagination(); // Policies state for filtering @@ -179,8 +186,9 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { // Agent enrollment flyout state const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState(false); - // Agent reassignment flyout state - const [agentToReassignId, setAgentToReassignId] = useState(undefined); + // Agent actions states + const [agentToReassign, setAgentToReassign] = useState(undefined); + const [agentToUnenroll, setAgentToUnenroll] = useState(undefined); let kuery = search.trim(); if (selectedAgentPolicies.length) { @@ -229,6 +237,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { const agents = agentsRequest.data ? agentsRequest.data.list : []; const totalAgents = agentsRequest.data ? agentsRequest.data.total : 0; + const totalInactiveAgents = agentsRequest.data ? agentsRequest.data.totalInactive : 0; const { isLoading } = agentsRequest; const agentPoliciesRequest = useGetAgentPolicies({ @@ -345,7 +354,8 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { agentsRequest.resendRequest()} - onReassignClick={() => setAgentToReassignId(agent.id)} + onReassignClick={() => setAgentToReassign(agent)} + onUnenrollClick={() => setAgentToUnenroll(agent)} /> ); }, @@ -378,8 +388,6 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { /> ); - const agentToReassign = agentToReassignId && agents.find((a) => a.id === agentToReassignId); - return ( <> {isEnrollmentFlyoutOpen ? ( @@ -391,15 +399,30 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { {agentToReassign && ( { - setAgentToReassignId(undefined); + setAgentToReassign(undefined); agentsRequest.resendRequest(); }} /> )} - + {agentToUnenroll && ( + + { + setAgentToUnenroll(undefined); + agentsRequest.resendRequest(); + }} + useForceUnenroll={agentToUnenroll.status === 'unenrolling'} + /> + + )} + + {/* Search and filter bar */} + @@ -510,9 +533,31 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { - + + {/* Agent total and bulk actions */} + agent.active).length || 0} + selectionMode={selectionMode} + setSelectionMode={setSelectionMode} + currentQuery={kuery} + selectedAgents={selectedAgents} + setSelectedAgents={(newAgents: Agent[]) => { + if (tableRef?.current) { + tableRef.current.setSelection(newAgents); + setSelectionMode('manual'); + } + }} + refreshAgents={() => agentsRequest.resendRequest()} + /> + + + + {/* Agent list table */} + ref={tableRef} className="fleet__agentList__table" data-test-subj="fleetAgentListTable" loading={isLoading} @@ -551,6 +596,18 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { totalItemCount: totalAgents, pageSizeOptions, }} + isSelectable={true} + selection={ + isGoldPlus + ? { + onSelectionChange: (newAgents: Agent[]) => { + setSelectedAgents(newAgents); + setSelectionMode('manual'); + }, + selectable: (agent: Agent) => agent.active, + } + : undefined + } onChange={({ page }: { page: { index: number; size: number } }) => { const newPagination = { ...pagination, diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_reassign_policy_flyout/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_reassign_policy_flyout/index.tsx index 0c154bf1074c0..d3af1287c4025 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_reassign_policy_flyout/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_reassign_policy_flyout/index.tsx @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlyout, @@ -22,40 +22,55 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { Agent } from '../../../../types'; -import { sendPutAgentReassign, useCore, useGetAgentPolicies } from '../../../../hooks'; +import { + sendPutAgentReassign, + sendPostBulkAgentReassign, + useCore, + useGetAgentPolicies, +} from '../../../../hooks'; import { AgentPolicyPackageBadges } from '../agent_policy_package_badges'; interface Props { onClose: () => void; - agent: Agent; + agents: Agent[] | string; } export const AgentReassignAgentPolicyFlyout: React.FunctionComponent = ({ onClose, - agent, + agents, }) => { const { notifications } = useCore(); + const isSingleAgent = Array.isArray(agents) && agents.length === 1; + const [selectedAgentPolicyId, setSelectedAgentPolicyId] = useState( - agent.policy_id + isSingleAgent ? (agents[0] as Agent).policy_id : undefined ); - const agentPoliciesRequest = useGetAgentPolicies({ page: 1, perPage: 1000, }); const agentPolicies = agentPoliciesRequest.data ? agentPoliciesRequest.data.items : []; + useEffect(() => { + if (!selectedAgentPolicyId && agentPolicies[0]) { + setSelectedAgentPolicyId(agentPolicies[0].id); + } + }, [agentPolicies, selectedAgentPolicyId]); const [isSubmitting, setIsSubmitting] = useState(false); - async function onSubmit() { try { setIsSubmitting(true); if (!selectedAgentPolicyId) { throw new Error('No selected agent policy id'); } - const res = await sendPutAgentReassign(agent.id, { - policy_id: selectedAgentPolicyId, - }); + const res = isSingleAgent + ? await sendPutAgentReassign((agents[0] as Agent).id, { + policy_id: selectedAgentPolicyId, + }) + : await sendPostBulkAgentReassign({ + policy_id: selectedAgentPolicyId, + agents: Array.isArray(agents) ? agents.map((agent) => agent.id) : agents, + }); if (res.error) { throw res.error; } @@ -91,7 +106,10 @@ export const AgentReassignAgentPolicyFlyout: React.FunctionComponent = ({ @@ -106,6 +124,7 @@ export const AgentReassignAgentPolicyFlyout: React.FunctionComponent = ({ > ({ value: agentPolicy.id, text: agentPolicy.name, @@ -134,7 +153,7 @@ export const AgentReassignAgentPolicyFlyout: React.FunctionComponent = ({ void; + agents: Agent[] | string; + agentCount: number; + useForceUnenroll?: boolean; +} + +export const AgentUnenrollAgentModal: React.FunctionComponent = ({ + onClose, + agents, + agentCount, + useForceUnenroll, +}) => { + const { notifications } = useCore(); + const [forceUnenroll, setForceUnenroll] = useState(useForceUnenroll || false); + const [isSubmitting, setIsSubmitting] = useState(false); + const isSingleAgent = Array.isArray(agents) && agents.length === 1; + + async function onSubmit() { + try { + setIsSubmitting(true); + const { error } = isSingleAgent + ? await sendPostAgentUnenroll((agents[0] as Agent).id, { + force: forceUnenroll, + }) + : await sendPostBulkAgentUnenroll({ + agents: Array.isArray(agents) ? agents.map((agent) => agent.id) : agents, + force: forceUnenroll, + }); + if (error) { + throw error; + } + setIsSubmitting(false); + if (forceUnenroll) { + const successMessage = isSingleAgent + ? i18n.translate( + 'xpack.ingestManager.unenrollAgents.successForceSingleNotificationTitle', + { defaultMessage: 'Agent unenrolled' } + ) + : i18n.translate( + 'xpack.ingestManager.unenrollAgents.successForceMultiNotificationTitle', + { defaultMessage: 'Agents unenrolled' } + ); + notifications.toasts.addSuccess(successMessage); + } else { + const successMessage = isSingleAgent + ? i18n.translate('xpack.ingestManager.unenrollAgents.successSingleNotificationTitle', { + defaultMessage: 'Unenrolling agent', + }) + : i18n.translate('xpack.ingestManager.unenrollAgents.successMultiNotificationTitle', { + defaultMessage: 'Unenrolling agents', + }); + notifications.toasts.addSuccess(successMessage); + } + onClose(); + } catch (error) { + setIsSubmitting(false); + notifications.toasts.addError(error, { + title: i18n.translate('xpack.ingestManager.unenrollAgents.fatalErrorNotificationTitle', { + defaultMessage: 'Error unenrolling {count, plural, one {agent} other {agents}}', + values: { count: agentCount }, + }), + }); + } + } + + return ( + + + ) : ( + + ) + } + onCancel={onClose} + onConfirm={onSubmit} + cancelButtonText={ + + } + confirmButtonDisabled={isSubmitting} + confirmButtonText={ + isSingleAgent ? ( + + ) : ( + + ) + } + buttonColor="danger" + > +

+ {isSingleAgent ? ( + + ) : ( + + )} +

+ + ), + }} + > + + } + checked={forceUnenroll} + onChange={(e) => setForceUnenroll(e.target.checked)} + disabled={useForceUnenroll} + /> + +
+
+ ); +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_unenroll_provider.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_unenroll_provider.tsx deleted file mode 100644 index 6f1cba70bbcee..0000000000000 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_unenroll_provider.tsx +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { Fragment, useRef, useState } from 'react'; -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { useCore, sendRequest } from '../../../hooks'; -import { PostAgentUnenrollResponse } from '../../../types'; -import { agentRouteService } from '../../../services'; - -interface Props { - children: (unenrollAgents: UnenrollAgents) => React.ReactElement; - forceUnenroll?: boolean; -} - -export type UnenrollAgents = ( - agents: string[] | string, - agentsCount: number, - onSuccess?: OnSuccessCallback -) => void; - -type OnSuccessCallback = (agentsUnenrolled: string[]) => void; - -export const AgentUnenrollProvider: React.FunctionComponent = ({ - children, - forceUnenroll = false, -}) => { - const core = useCore(); - const [agents, setAgents] = useState([]); - const [agentsCount, setAgentsCount] = useState(0); - const [isModalOpen, setIsModalOpen] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const onSuccessCallback = useRef(null); - - const unenrollAgentsPrompt: UnenrollAgents = ( - agentsToUnenroll, - agentsToUnenrollCount, - onSuccess = () => undefined - ) => { - if ( - agentsToUnenroll === undefined || - // !Only supports unenrolling one agent - (Array.isArray(agentsToUnenroll) && agentsToUnenroll.length !== 1) - ) { - throw new Error('No agents specified for unenrollment'); - } - setIsModalOpen(true); - setAgents(agentsToUnenroll); - setAgentsCount(agentsToUnenrollCount); - onSuccessCallback.current = onSuccess; - }; - - const closeModal = () => { - setAgents([]); - setAgentsCount(0); - setIsLoading(false); - setIsModalOpen(false); - }; - - const unenrollAgents = async () => { - setIsLoading(true); - - try { - const agentId = agents[0]; - const { error } = await sendRequest({ - path: agentRouteService.getUnenrollPath(agentId), - method: 'post', - body: { - force: forceUnenroll, - }, - }); - - if (error) { - throw new Error(error.message); - } - - const successMessage = forceUnenroll - ? i18n.translate('xpack.ingestManager.unenrollAgents.successForceSingleNotificationTitle', { - defaultMessage: "Agent '{id}' unenrolled", - values: { id: agentId }, - }) - : i18n.translate('xpack.ingestManager.unenrollAgents.successSingleNotificationTitle', { - defaultMessage: "Unenrolling agent '{id}'", - values: { id: agentId }, - }); - core.notifications.toasts.addSuccess(successMessage); - - if (onSuccessCallback.current) { - onSuccessCallback.current([agentId]); - } - } catch (e) { - core.notifications.toasts.addDanger( - i18n.translate('xpack.ingestManager.unenrollAgents.fatalErrorNotificationTitle', { - defaultMessage: 'Error unenrolling agents', - }) - ); - } - - closeModal(); - }; - - const renderModal = () => { - if (!isModalOpen) { - return null; - } - - const unenrollByKuery = typeof agents === 'string'; - const isSingle = agentsCount === 1; - - return ( - - - ) : ( - - ) - ) : ( - - ) - } - onCancel={closeModal} - onConfirm={unenrollAgents} - cancelButtonText={ - - } - confirmButtonText={ - isLoading ? ( - - ) : ( - - ) - } - buttonColor="danger" - confirmButtonDisabled={isLoading} - /> - - ); - }; - - return ( - - {children(unenrollAgentsPrompt)} - {renderModal()} - - ); -}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/index.tsx index 527f920f24365..eea4ed3b712b1 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/index.tsx @@ -8,4 +8,4 @@ export * from './loading'; export * from './agent_reassign_policy_flyout'; export * from './agent_enrollment_flyout'; export * from './agent_health'; -export * from './agent_unenroll_provider'; +export * from './agent_unenroll_modal'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts index 2c9e8b84d4069..ed6ba5c891a0b 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts @@ -25,4 +25,5 @@ export { isPackageLimited, doesAgentPolicyAlreadyIncludePackage, isValidNamespace, + LicenseService, } from '../../../../common'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts index 30a6742af6ea6..71a44089b8bf7 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts @@ -49,13 +49,18 @@ export { GetAgentsResponse, GetAgentsRequest, GetOneAgentResponse, + PostAgentUnenrollRequest, PostAgentUnenrollResponse, + PostBulkAgentUnenrollRequest, + PostBulkAgentUnenrollResponse, GetOneAgentEventsRequest, GetOneAgentEventsResponse, GetAgentStatusRequest, GetAgentStatusResponse, PutAgentReassignRequest, PutAgentReassignResponse, + PostBulkAgentReassignRequest, + PostBulkAgentReassignResponse, // API schemas - Enrollment API Keys GetEnrollmentAPIKeysResponse, GetEnrollmentAPIKeysRequest, diff --git a/x-pack/plugins/ingest_manager/public/plugin.ts b/x-pack/plugins/ingest_manager/public/plugin.ts index 536832cdaed64..5f7bfe865e892 100644 --- a/x-pack/plugins/ingest_manager/public/plugin.ts +++ b/x-pack/plugins/ingest_manager/public/plugin.ts @@ -23,7 +23,7 @@ import { BASE_PATH } from './applications/ingest_manager/constants'; import { IngestManagerConfigType } from '../common/types'; import { setupRouteService, appRoutesService } from '../common'; -import { setHttpClient } from './applications/ingest_manager/hooks'; +import { setHttpClient, licenseService } from './applications/ingest_manager/hooks'; import { TutorialDirectoryNotice, TutorialDirectoryHeaderLink, @@ -71,6 +71,9 @@ export class IngestManagerPlugin // Set up http client setHttpClient(core.http); + // Set up license service + licenseService.start(deps.licensing.license$); + // Register main Ingest Manager app core.application.register({ id: PLUGIN_ID, diff --git a/x-pack/plugins/ingest_manager/server/errors/handlers.ts b/x-pack/plugins/ingest_manager/server/errors/handlers.ts index 9f776565cf262..b621f2dd29331 100644 --- a/x-pack/plugins/ingest_manager/server/errors/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/errors/handlers.ts @@ -56,10 +56,7 @@ const getHTTPResponseCode = (error: IngestManagerError): number => { return 400; // Bad Request }; -export const defaultIngestErrorHandler: IngestErrorHandler = async ({ - error, - response, -}: IngestErrorHandlerParams): Promise => { +export function ingestErrorToResponseOptions(error: IngestErrorHandlerParams['error']) { const logger = appContextService.getLogger(); if (isLegacyESClientError(error)) { // there was a problem communicating with ES (e.g. via `callCluster`) @@ -72,36 +69,44 @@ export const defaultIngestErrorHandler: IngestErrorHandler = async ({ logger.error(message); - return response.customError({ + return { statusCode: error?.statusCode || error.status, body: { message }, - }); + }; } // our "expected" errors if (error instanceof IngestManagerError) { // only log the message logger.error(error.message); - return response.customError({ + return { statusCode: getHTTPResponseCode(error), body: { message: error.message }, - }); + }; } // handle any older Boom-based errors or the few places our app uses them if (isBoom(error)) { // only log the message logger.error(error.output.payload.message); - return response.customError({ + return { statusCode: error.output.statusCode, body: { message: error.output.payload.message }, - }); + }; } // not sure what type of error this is. log as much as possible logger.error(error); - return response.customError({ + return { statusCode: 500, body: { message: error.message }, - }); + }; +} + +export const defaultIngestErrorHandler: IngestErrorHandler = async ({ + error, + response, +}: IngestErrorHandlerParams): Promise => { + const options = ingestErrorToResponseOptions(error); + return response.customError(options); }; diff --git a/x-pack/plugins/ingest_manager/server/errors/index.ts b/x-pack/plugins/ingest_manager/server/errors/index.ts index 5e36a2ec9a884..f495bf551dcff 100644 --- a/x-pack/plugins/ingest_manager/server/errors/index.ts +++ b/x-pack/plugins/ingest_manager/server/errors/index.ts @@ -5,7 +5,7 @@ */ /* eslint-disable max-classes-per-file */ -export { defaultIngestErrorHandler } from './handlers'; +export { defaultIngestErrorHandler, ingestErrorToResponseOptions } from './handlers'; export class IngestManagerError extends Error { constructor(message?: string) { diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts index 2ebb7a0667aab..fb867af513fdc 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts @@ -16,6 +16,7 @@ import { GetAgentStatusResponse, PutAgentReassignResponse, PostAgentEnrollRequest, + PostBulkAgentReassignResponse, } from '../../../common/types'; import { GetAgentsRequestSchema, @@ -26,11 +27,13 @@ import { PostAgentCheckinRequest, GetAgentStatusRequestSchema, PutAgentReassignRequestSchema, + PostBulkAgentReassignRequestSchema, } from '../../types'; +import { defaultIngestErrorHandler } from '../../errors'; +import { licenseService } from '../../services'; import * as AgentService from '../../services/agents'; import * as APIKeyService from '../../services/api_keys'; import { appContextService } from '../../services/app_context'; -import { defaultIngestErrorHandler } from '../../errors'; export const getAgentHandler: RequestHandler ({ @@ -245,6 +253,7 @@ export const getAgentsHandler: RequestHandler< status: AgentService.getAgentStatus(agent), })), total, + totalInactive, page, perPage, }; @@ -270,6 +279,47 @@ export const putAgentsReassignHandler: RequestHandler< } }; +export const postBulkAgentsReassignHandler: RequestHandler< + undefined, + undefined, + TypeOf +> = async (context, request, response) => { + if (!licenseService.isGoldPlus()) { + return response.customError({ + statusCode: 403, + body: { message: 'Requires Gold license' }, + }); + } + + const soClient = context.core.savedObjects.client; + try { + // Reassign by array of IDs + const result = Array.isArray(request.body.agents) + ? await AgentService.reassignAgents( + soClient, + { agentIds: request.body.agents }, + request.body.policy_id + ) + : await AgentService.reassignAgents( + soClient, + { kuery: request.body.agents }, + request.body.policy_id + ); + const body: PostBulkAgentReassignResponse = result.saved_objects.reduce((acc, so) => { + return { + ...acc, + [so.id]: { + success: !so.error, + error: so.error || undefined, + }, + }; + }, {}); + return response.ok({ body }); + } catch (error) { + return defaultIngestErrorHandler({ error, response }); + } +}; + export const getAgentStatusForAgentPolicyHandler: RequestHandler< undefined, TypeOf diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/index.ts b/x-pack/plugins/ingest_manager/server/routes/agent/index.ts index a2e5c742ad6b5..73ed276ba02e7 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/index.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/index.ts @@ -23,10 +23,13 @@ import { PostAgentAcksRequestParamsJSONSchema, PostAgentAcksRequestBodyJSONSchema, PostAgentUnenrollRequestSchema, + PostBulkAgentUnenrollRequestSchema, GetAgentStatusRequestSchema, PostNewAgentActionRequestSchema, PutAgentReassignRequestSchema, + PostBulkAgentReassignRequestSchema, PostAgentEnrollRequestBodyJSONSchema, + PostAgentUpgradeRequestSchema, } from '../../types'; import { getAgentsHandler, @@ -38,13 +41,15 @@ import { postAgentEnrollHandler, getAgentStatusForAgentPolicyHandler, putAgentsReassignHandler, + postBulkAgentsReassignHandler, } from './handlers'; import { postAgentAcksHandlerBuilder } from './acks_handlers'; import * as AgentService from '../../services/agents'; import { postNewAgentActionHandlerBuilder } from './actions_handlers'; import { appContextService } from '../../services'; -import { postAgentsUnenrollHandler } from './unenroll_handler'; +import { postAgentUnenrollHandler, postBulkAgentsUnenrollHandler } from './unenroll_handler'; import { IngestManagerConfigType } from '../..'; +import { postAgentUpgradeHandler } from './upgrade_handler'; const ajv = new Ajv({ coerceTypes: true, @@ -181,7 +186,7 @@ export const registerRoutes = (router: IRouter, config: IngestManagerConfigType) validate: PostAgentUnenrollRequestSchema, options: { tags: [`access:${PLUGIN_ID}-all`] }, }, - postAgentsUnenrollHandler + postAgentUnenrollHandler ); router.put( @@ -212,4 +217,32 @@ export const registerRoutes = (router: IRouter, config: IngestManagerConfigType) }, getAgentStatusForAgentPolicyHandler ); + // upgrade agent + router.post( + { + path: AGENT_API_ROUTES.UPGRADE_PATTERN, + validate: PostAgentUpgradeRequestSchema, + options: { tags: [`access:${PLUGIN_ID}-all`] }, + }, + postAgentUpgradeHandler + ); + // Bulk reassign + router.post( + { + path: AGENT_API_ROUTES.BULK_REASSIGN_PATTERN, + validate: PostBulkAgentReassignRequestSchema, + options: { tags: [`access:${PLUGIN_ID}-all`] }, + }, + postBulkAgentsReassignHandler + ); + + // Bulk unenroll + router.post( + { + path: AGENT_API_ROUTES.BULK_UNENROLL_PATTERN, + validate: PostBulkAgentUnenrollRequestSchema, + options: { tags: [`access:${PLUGIN_ID}-all`] }, + }, + postBulkAgentsUnenrollHandler + ); }; diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/unenroll_handler.ts b/x-pack/plugins/ingest_manager/server/routes/agent/unenroll_handler.ts index fa200e912d625..861d7c45c6f0a 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/unenroll_handler.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/unenroll_handler.ts @@ -6,12 +6,13 @@ import { RequestHandler } from 'src/core/server'; import { TypeOf } from '@kbn/config-schema'; -import { PostAgentUnenrollResponse } from '../../../common/types'; -import { PostAgentUnenrollRequestSchema } from '../../types'; +import { PostAgentUnenrollResponse, PostBulkAgentUnenrollResponse } from '../../../common/types'; +import { PostAgentUnenrollRequestSchema, PostBulkAgentUnenrollRequestSchema } from '../../types'; +import { licenseService } from '../../services'; import * as AgentService from '../../services/agents'; import { defaultIngestErrorHandler } from '../../errors'; -export const postAgentsUnenrollHandler: RequestHandler< +export const postAgentUnenrollHandler: RequestHandler< TypeOf, undefined, TypeOf @@ -30,3 +31,32 @@ export const postAgentsUnenrollHandler: RequestHandler< return defaultIngestErrorHandler({ error, response }); } }; + +export const postBulkAgentsUnenrollHandler: RequestHandler< + undefined, + undefined, + TypeOf +> = async (context, request, response) => { + if (!licenseService.isGoldPlus()) { + return response.customError({ + statusCode: 403, + body: { message: 'Requires Gold license' }, + }); + } + const soClient = context.core.savedObjects.client; + const unenrollAgents = + request.body?.force === true ? AgentService.forceUnenrollAgents : AgentService.unenrollAgents; + + try { + if (Array.isArray(request.body.agents)) { + await unenrollAgents(soClient, { agentIds: request.body.agents }); + } else { + await unenrollAgents(soClient, { kuery: request.body.agents }); + } + + const body: PostBulkAgentUnenrollResponse = {}; + return response.ok({ body }); + } catch (error) { + return defaultIngestErrorHandler({ error, response }); + } +}; diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/upgrade_handler.ts b/x-pack/plugins/ingest_manager/server/routes/agent/upgrade_handler.ts new file mode 100644 index 0000000000000..e5d7a44c00768 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/routes/agent/upgrade_handler.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RequestHandler } from 'src/core/server'; +import { TypeOf } from '@kbn/config-schema'; +import { PostAgentUpgradeResponse } from '../../../common/types'; +import { PostAgentUpgradeRequestSchema } from '../../types'; +import * as AgentService from '../../services/agents'; +import { appContextService } from '../../services'; +import { defaultIngestErrorHandler } from '../../errors'; + +export const postAgentUpgradeHandler: RequestHandler< + TypeOf, + undefined, + TypeOf +> = async (context, request, response) => { + const soClient = context.core.savedObjects.client; + const { version, source_uri: sourceUri } = request.body; + + // temporarily only allow upgrading to the same version as the installed kibana version + const kibanaVersion = appContextService.getKibanaVersion(); + if (kibanaVersion !== version) { + return response.customError({ + statusCode: 400, + body: { + message: `cannot upgrade agent to ${version} because it is different than the installed kibana version ${kibanaVersion}`, + }, + }); + } + + try { + await AgentService.sendUpgradeAgentAction({ + soClient, + agentId: request.params.agentId, + version, + sourceUri, + }); + + const body: PostAgentUpgradeResponse = {}; + return response.ok({ body }); + } catch (error) { + return defaultIngestErrorHandler({ error, response }); + } +}; diff --git a/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts index c40e0e4ac5c0b..7ae896c1f30a6 100644 --- a/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts @@ -5,7 +5,6 @@ */ import { TypeOf } from '@kbn/config-schema'; import { RequestHandler, CustomHttpResponseOptions } from 'src/core/server'; -import { appContextService } from '../../services'; import { GetInfoResponse, InstallPackageResponse, @@ -14,6 +13,7 @@ import { GetCategoriesResponse, GetPackagesResponse, GetLimitedPackagesResponse, + BulkInstallPackagesResponse, } from '../../../common'; import { GetCategoriesRequestSchema, @@ -23,6 +23,7 @@ import { InstallPackageFromRegistryRequestSchema, InstallPackageByUploadRequestSchema, DeletePackageRequestSchema, + BulkUpgradePackagesFromRegistryRequestSchema, } from '../../types'; import { getCategories, @@ -34,9 +35,12 @@ import { getLimitedPackages, getInstallationObject, } from '../../services/epm/packages'; -import { IngestManagerError, defaultIngestErrorHandler } from '../../errors'; +import { defaultIngestErrorHandler } from '../../errors'; import { splitPkgKey } from '../../services/epm/registry'; -import { getInstallType } from '../../services/epm/packages/install'; +import { + handleInstallPackageFailure, + bulkInstallPackages, +} from '../../services/epm/packages/install'; export const getCategoriesHandler: RequestHandler< undefined, @@ -136,13 +140,11 @@ export const installPackageFromRegistryHandler: RequestHandler< undefined, TypeOf > = async (context, request, response) => { - const logger = appContextService.getLogger(); const savedObjectsClient = context.core.savedObjects.client; const callCluster = context.core.elasticsearch.legacy.client.callAsCurrentUser; const { pkgkey } = request.params; const { pkgName, pkgVersion } = splitPkgKey(pkgkey); const installedPkg = await getInstallationObject({ savedObjectsClient, pkgName }); - const installType = getInstallType({ pkgVersion, installedPkg }); try { const res = await installPackage({ savedObjectsClient, @@ -155,36 +157,38 @@ export const installPackageFromRegistryHandler: RequestHandler< }; return response.ok({ body }); } catch (e) { - // could have also done `return defaultIngestErrorHandler({ error: e, response })` at each of the returns, - // but doing it this way will log the outer/install errors before any inner/rollback errors const defaultResult = await defaultIngestErrorHandler({ error: e, response }); - if (e instanceof IngestManagerError) { - return defaultResult; - } + await handleInstallPackageFailure({ + savedObjectsClient, + error: e, + pkgName, + pkgVersion, + installedPkg, + callCluster, + }); - // if there is an unknown server error, uninstall any package assets or reinstall the previous version if update - try { - if (installType === 'install' || installType === 'reinstall') { - logger.error(`uninstalling ${pkgkey} after error installing`); - await removeInstallation({ savedObjectsClient, pkgkey, callCluster }); - } - if (installType === 'update') { - // @ts-ignore getInstallType ensures we have installedPkg - const prevVersion = `${pkgName}-${installedPkg.attributes.version}`; - logger.error(`rolling back to ${prevVersion} after error installing ${pkgkey}`); - await installPackage({ - savedObjectsClient, - pkgkey: prevVersion, - callCluster, - }); - } - } catch (error) { - logger.error(`failed to uninstall or rollback package after installation error ${error}`); - } return defaultResult; } }; +export const bulkInstallPackagesFromRegistryHandler: RequestHandler< + undefined, + undefined, + TypeOf +> = async (context, request, response) => { + const savedObjectsClient = context.core.savedObjects.client; + const callCluster = context.core.elasticsearch.legacy.client.callAsCurrentUser; + const res = await bulkInstallPackages({ + savedObjectsClient, + callCluster, + packagesToUpgrade: request.body.packages, + }); + const body: BulkInstallPackagesResponse = { + response: res, + }; + return response.ok({ body }); +}; + export const installPackageByUploadHandler: RequestHandler< undefined, undefined, diff --git a/x-pack/plugins/ingest_manager/server/routes/epm/index.ts b/x-pack/plugins/ingest_manager/server/routes/epm/index.ts index 9048652f0e8a9..eaf61335b5e06 100644 --- a/x-pack/plugins/ingest_manager/server/routes/epm/index.ts +++ b/x-pack/plugins/ingest_manager/server/routes/epm/index.ts @@ -14,6 +14,7 @@ import { installPackageFromRegistryHandler, installPackageByUploadHandler, deletePackageHandler, + bulkInstallPackagesFromRegistryHandler, } from './handlers'; import { GetCategoriesRequestSchema, @@ -23,6 +24,7 @@ import { InstallPackageFromRegistryRequestSchema, InstallPackageByUploadRequestSchema, DeletePackageRequestSchema, + BulkUpgradePackagesFromRegistryRequestSchema, } from '../../types'; const MAX_FILE_SIZE_BYTES = 104857600; // 100MB @@ -82,6 +84,15 @@ export const registerRoutes = (router: IRouter) => { installPackageFromRegistryHandler ); + router.post( + { + path: EPM_API_ROUTES.BULK_INSTALL_PATTERN, + validate: BulkUpgradePackagesFromRegistryRequestSchema, + options: { tags: [`access:${PLUGIN_ID}-all`] }, + }, + bulkInstallPackagesFromRegistryHandler + ); + router.post( { path: EPM_API_ROUTES.INSTALL_BY_UPLOAD_PATTERN, diff --git a/x-pack/plugins/ingest_manager/server/saved_objects/index.ts b/x-pack/plugins/ingest_manager/server/saved_objects/index.ts index e86f7b24e2c78..fd08b76a3916b 100644 --- a/x-pack/plugins/ingest_manager/server/saved_objects/index.ts +++ b/x-pack/plugins/ingest_manager/server/saved_objects/index.ts @@ -68,6 +68,8 @@ const savedObjectTypes: { [key: string]: SavedObjectsType } = { enrolled_at: { type: 'date' }, unenrolled_at: { type: 'date' }, unenrollment_started_at: { type: 'date' }, + upgraded_at: { type: 'date' }, + upgrade_started_at: { type: 'date' }, access_api_key_id: { type: 'keyword' }, version: { type: 'keyword' }, user_provided_metadata: { type: 'flattened' }, diff --git a/x-pack/plugins/ingest_manager/server/services/agent_policy.ts b/x-pack/plugins/ingest_manager/server/services/agent_policy.ts index 938cfb4351630..64b11512fae10 100644 --- a/x-pack/plugins/ingest_manager/server/services/agent_policy.ts +++ b/x-pack/plugins/ingest_manager/server/services/agent_policy.ts @@ -26,6 +26,7 @@ import { packagePolicyService } from './package_policy'; import { outputService } from './output'; import { agentPolicyUpdateEventHandler } from './agent_policy_update'; import { getSettings } from './settings'; +import { normalizeKuery } from './saved_object'; const SAVED_OBJECT_TYPE = AGENT_POLICY_SAVED_OBJECT_TYPE; @@ -166,13 +167,7 @@ class AgentPolicyService { sortOrder, page, perPage, - // To ensure users don't need to know about SO data structure... - filter: kuery - ? kuery.replace( - new RegExp(`${SAVED_OBJECT_TYPE}\.`, 'g'), - `${SAVED_OBJECT_TYPE}.attributes.` - ) - : undefined, + filter: kuery ? normalizeKuery(SAVED_OBJECT_TYPE, kuery) : undefined, }); const agentPolicies = await Promise.all( diff --git a/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts b/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts index 866aa587b8a56..c7b4098803827 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts @@ -57,7 +57,7 @@ describe('test agent acks services', () => { ); }); - it('should update config field on the agent if a policy change is acknowledged', async () => { + it('should update config field on the agent if a policy change is acknowledged with an agent without policy', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); const actionAttributes = { @@ -116,6 +116,114 @@ describe('test agent acks services', () => { `); }); + it('should update config field on the agent if a policy change is acknowledged with a higher revision than the agent one', async () => { + const mockSavedObjectsClient = savedObjectsClientMock.create(); + + const actionAttributes = { + type: 'CONFIG_CHANGE', + policy_id: 'policy1', + policy_revision: 4, + sent_at: '2020-03-14T19:45:02.620Z', + timestamp: '2019-01-04T14:32:03.36764-05:00', + created_at: '2020-03-14T19:45:02.620Z', + ack_data: JSON.stringify({ packages: ['system'] }), + }; + + mockSavedObjectsClient.bulkGet.mockReturnValue( + Promise.resolve({ + saved_objects: [ + { + id: 'action2', + references: [], + type: AGENT_ACTION_SAVED_OBJECT_TYPE, + attributes: actionAttributes, + }, + ], + } as SavedObjectsBulkResponse) + ); + + await acknowledgeAgentActions( + mockSavedObjectsClient, + ({ + id: 'id', + type: AGENT_TYPE_PERMANENT, + policy_id: 'policy1', + policy_revision: 3, + } as unknown) as Agent, + [ + { + type: 'ACTION_RESULT', + subtype: 'CONFIG', + timestamp: '2019-01-04T14:32:03.36764-05:00', + action_id: 'action2', + agent_id: 'id', + } as AgentEvent, + ] + ); + expect(mockSavedObjectsClient.bulkUpdate).toBeCalled(); + expect(mockSavedObjectsClient.bulkUpdate.mock.calls[0][0]).toHaveLength(1); + expect(mockSavedObjectsClient.bulkUpdate.mock.calls[0][0][0]).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "packages": Array [ + "system", + ], + "policy_revision": 4, + }, + "id": "id", + "type": "fleet-agents", + } + `); + }); + + it('should not update config field on the agent if a policy change is acknowledged with a lower revision than the agent one', async () => { + const mockSavedObjectsClient = savedObjectsClientMock.create(); + + const actionAttributes = { + type: 'CONFIG_CHANGE', + policy_id: 'policy1', + policy_revision: 4, + sent_at: '2020-03-14T19:45:02.620Z', + timestamp: '2019-01-04T14:32:03.36764-05:00', + created_at: '2020-03-14T19:45:02.620Z', + ack_data: JSON.stringify({ packages: ['system'] }), + }; + + mockSavedObjectsClient.bulkGet.mockReturnValue( + Promise.resolve({ + saved_objects: [ + { + id: 'action2', + references: [], + type: AGENT_ACTION_SAVED_OBJECT_TYPE, + attributes: actionAttributes, + }, + ], + } as SavedObjectsBulkResponse) + ); + + await acknowledgeAgentActions( + mockSavedObjectsClient, + ({ + id: 'id', + type: AGENT_TYPE_PERMANENT, + policy_id: 'policy1', + policy_revision: 5, + } as unknown) as Agent, + [ + { + type: 'ACTION_RESULT', + subtype: 'CONFIG', + timestamp: '2019-01-04T14:32:03.36764-05:00', + action_id: 'action2', + agent_id: 'id', + } as AgentEvent, + ] + ); + expect(mockSavedObjectsClient.bulkUpdate).toBeCalled(); + expect(mockSavedObjectsClient.bulkUpdate.mock.calls[0][0]).toHaveLength(0); + }); + it('should not update config field on the agent if a policy change for an old revision is acknowledged', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); diff --git a/x-pack/plugins/ingest_manager/server/services/agents/acks.ts b/x-pack/plugins/ingest_manager/server/services/agents/acks.ts index d29dfcec7ef30..e22ee4256b0e2 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/acks.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/acks.ts @@ -28,6 +28,7 @@ import { } from '../../constants'; import { getAgentActionByIds } from './actions'; import { forceUnenrollAgent } from './unenroll'; +import { ackAgentUpgraded } from './upgrade'; const ALLOWED_ACKNOWLEDGEMENT_TYPE: string[] = ['ACTION_RESULT']; @@ -80,6 +81,11 @@ export async function acknowledgeAgentActions( await forceUnenrollAgent(soClient, agent.id); } + const upgradeAction = actions.find((action) => action.type === 'UPGRADE'); + if (upgradeAction) { + await ackAgentUpgraded(soClient, upgradeAction); + } + const configChangeAction = getLatestConfigChangePolicyActionIfUpdated(agent, actions); await soClient.bulkUpdate([ @@ -139,16 +145,12 @@ function getLatestConfigChangePolicyActionIfUpdated( !isAgentPolicyAction(action) || action.type !== 'CONFIG_CHANGE' || action.policy_id !== agent.policy_id || - (acc?.policy_revision ?? 0) < (agent.policy_revision || 0) + (action?.policy_revision ?? 0) < (agent.policy_revision || 0) ) { return acc; } - if (action.policy_revision > (acc?.policy_revision ?? 0)) { - return action; - } - - return acc; + return action; }, null); } diff --git a/x-pack/plugins/ingest_manager/server/services/agents/actions.ts b/x-pack/plugins/ingest_manager/server/services/agents/actions.ts index 254c2c8b21e32..f018eea61e4f3 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/actions.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/actions.ts @@ -29,12 +29,20 @@ export async function createAgentAction( return createAction(soClient, newAgentAction); } +export async function bulkCreateAgentActions( + soClient: SavedObjectsClientContract, + newAgentActions: Array> +): Promise { + return bulkCreateActions(soClient, newAgentActions); +} + export function createAgentPolicyAction( soClient: SavedObjectsClientContract, newAgentAction: Omit ): Promise { return createAction(soClient, newAgentAction); } + async function createAction( soClient: SavedObjectsClientContract, newAgentAction: Omit @@ -47,19 +55,25 @@ async function createAction( soClient: SavedObjectsClientContract, newAgentAction: Omit | Omit ): Promise { - const so = await soClient.create(AGENT_ACTION_SAVED_OBJECT_TYPE, { - ...newAgentAction, - data: newAgentAction.data ? JSON.stringify(newAgentAction.data) : undefined, - ack_data: newAgentAction.ack_data ? JSON.stringify(newAgentAction.ack_data) : undefined, - }); + const actionSO = await soClient.create( + AGENT_ACTION_SAVED_OBJECT_TYPE, + { + ...newAgentAction, + data: newAgentAction.data ? JSON.stringify(newAgentAction.data) : undefined, + ack_data: newAgentAction.ack_data ? JSON.stringify(newAgentAction.ack_data) : undefined, + } + ); - if (isAgentActionSavedObject(so)) { - const agentAction = savedObjectToAgentAction(so); + if (isAgentActionSavedObject(actionSO)) { + const agentAction = savedObjectToAgentAction(actionSO); + // Action `data` is encrypted, so is not returned from the saved object + // so we add back the original value from the request to form the expected + // response shape for POST create agent action endpoint agentAction.data = newAgentAction.data; return agentAction; - } else if (isPolicyActionSavedObject(so)) { - const agentAction = savedObjectToAgentAction(so); + } else if (isPolicyActionSavedObject(actionSO)) { + const agentAction = savedObjectToAgentAction(actionSO); agentAction.data = newAgentAction.data; return agentAction; @@ -67,6 +81,44 @@ async function createAction( throw new Error('Invalid action'); } +async function bulkCreateActions( + soClient: SavedObjectsClientContract, + newAgentActions: Array> +): Promise; +async function bulkCreateActions( + soClient: SavedObjectsClientContract, + newAgentActions: Array> +): Promise; +async function bulkCreateActions( + soClient: SavedObjectsClientContract, + newAgentActions: Array | Omit> +): Promise> { + const { saved_objects: actionSOs } = await soClient.bulkCreate( + newAgentActions.map((newAgentAction) => ({ + type: AGENT_ACTION_SAVED_OBJECT_TYPE, + attributes: { + ...newAgentAction, + data: newAgentAction.data ? JSON.stringify(newAgentAction.data) : undefined, + ack_data: newAgentAction.ack_data ? JSON.stringify(newAgentAction.ack_data) : undefined, + }, + })) + ); + + return actionSOs.map((actionSO) => { + if (isAgentActionSavedObject(actionSO)) { + const agentAction = savedObjectToAgentAction(actionSO); + // Compared to single create (createAction()), we don't add back the + // original value of `agentAction.data` as this method isn't exposed + // via an HTTP endpoint + return agentAction; + } else if (isPolicyActionSavedObject(actionSO)) { + const agentAction = savedObjectToAgentAction(actionSO); + return agentAction; + } + throw new Error('Invalid action'); + }); +} + export async function getAgentActionsForCheckin( soClient: SavedObjectsClientContract, agentId: string @@ -173,7 +225,11 @@ export async function getAgentPolicyActionByIds( ); } -export async function getNewActionsSince(soClient: SavedObjectsClientContract, timestamp: string) { +export async function getNewActionsSince( + soClient: SavedObjectsClientContract, + timestamp: string, + decryptData: boolean = true +) { const filter = nodeTypes.function.buildNode('and', [ nodeTypes.function.buildNode( 'not', @@ -191,14 +247,33 @@ export async function getNewActionsSince(soClient: SavedObjectsClientContract, t } ), ]); - const res = await soClient.find({ - type: AGENT_ACTION_SAVED_OBJECT_TYPE, - filter, - }); - return res.saved_objects + const actions = ( + await soClient.find({ + type: AGENT_ACTION_SAVED_OBJECT_TYPE, + filter, + }) + ).saved_objects .filter(isAgentActionSavedObject) .map((so) => savedObjectToAgentAction(so)); + + if (!decryptData) { + return actions; + } + + return await Promise.all( + actions.map(async (action) => { + // Get decrypted actions + return savedObjectToAgentAction( + await appContextService + .getEncryptedSavedObjects() + .getDecryptedAsInternalUser( + AGENT_ACTION_SAVED_OBJECT_TYPE, + action.id + ) + ); + }) + ); } export async function getLatestConfigChangeAction( diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/rxjs_utils.test.ts b/x-pack/plugins/ingest_manager/server/services/agents/checkin/rxjs_utils.test.ts new file mode 100644 index 0000000000000..5e84e3a50bb44 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/services/agents/checkin/rxjs_utils.test.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { TestScheduler } from 'rxjs/testing'; +import { createRateLimiter } from './rxjs_utils'; + +describe('createRateLimiter', () => { + it('should rate limit correctly with 1 request per 10ms', async () => { + const scheduler = new TestScheduler((actual, expected) => { + expect(actual).toEqual(expected); + }); + + scheduler.run(({ expectObservable, cold }) => { + const source = cold('a-b-c-d-e-f|'); + const rateLimiter = createRateLimiter(10, 1, 2, scheduler); + const obs = source.pipe(rateLimiter()); + const results = 'a 9ms b 9ms c 9ms d 9ms e 9ms (f|)'; + expectObservable(obs).toBe(results); + }); + }); +}); diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/rxjs_utils.ts b/x-pack/plugins/ingest_manager/server/services/agents/checkin/rxjs_utils.ts index dddade6841460..3bbfbbd4ec1bf 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/checkin/rxjs_utils.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/checkin/rxjs_utils.ts @@ -5,6 +5,7 @@ */ import * as Rx from 'rxjs'; +import { concatMap, delay } from 'rxjs/operators'; export class AbortError extends Error {} @@ -45,63 +46,35 @@ export const toPromiseAbortable = ( export function createRateLimiter( ratelimitIntervalMs: number, - ratelimitRequestPerInterval: number + ratelimitRequestPerInterval: number, + maxDelay: number, + scheduler = Rx.asyncScheduler ) { - function createCurrentInterval() { - return { - startedAt: Rx.asyncScheduler.now(), - numRequests: 0, - }; - } + let intervalEnd = 0; + let countInCurrentInterval = 0; - let currentInterval: { startedAt: number; numRequests: number } = createCurrentInterval(); - let observers: Array<[Rx.Subscriber, any]> = []; - let timerSubscription: Rx.Subscription | undefined; + function createRateLimitOperator(): Rx.OperatorFunction { + return Rx.pipe( + concatMap(function rateLimit(value: T) { + const now = scheduler.now(); + if (intervalEnd <= now) { + countInCurrentInterval = 1; + intervalEnd = now + ratelimitIntervalMs; + return Rx.of(value); + } else if (intervalEnd >= now + maxDelay) { + // re-rate limit in the future to avoid to schedule too far in the future as some observer can unsubscribe + return Rx.of(value).pipe(delay(maxDelay, scheduler), createRateLimitOperator()); + } else { + if (++countInCurrentInterval > ratelimitRequestPerInterval) { + countInCurrentInterval = 1; + intervalEnd += ratelimitIntervalMs; + } - function createTimeout() { - if (timerSubscription) { - return; - } - timerSubscription = Rx.asyncScheduler.schedule(() => { - timerSubscription = undefined; - currentInterval = createCurrentInterval(); - for (const [waitingObserver, value] of observers) { - if (currentInterval.numRequests >= ratelimitRequestPerInterval) { - createTimeout(); - continue; + const wait = intervalEnd - ratelimitIntervalMs - now; + return wait > 0 ? Rx.of(value).pipe(delay(wait, scheduler)) : Rx.of(value); } - currentInterval.numRequests++; - waitingObserver.next(value); - } - }, ratelimitIntervalMs); + }) + ); } - - return function limit(): Rx.MonoTypeOperatorFunction { - return (observable) => - new Rx.Observable((observer) => { - const subscription = observable.subscribe({ - next(value) { - if (currentInterval.numRequests < ratelimitRequestPerInterval) { - currentInterval.numRequests++; - observer.next(value); - return; - } - - observers = [...observers, [observer, value]]; - createTimeout(); - }, - error(err) { - observer.error(err); - }, - complete() { - observer.complete(); - }, - }); - - return () => { - observers = observers.filter((o) => o[0] !== observer); - subscription.unsubscribe(); - }; - }); - }; + return createRateLimitOperator; } diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts b/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts index 4122677a615ca..fbbed87b031e2 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/checkin/state_new_actions.ts @@ -10,7 +10,6 @@ import { shareReplay, distinctUntilKeyChanged, switchMap, - mergeMap, merge, filter, timeout, @@ -33,6 +32,8 @@ import { import { appContextService } from '../../app_context'; import { toPromiseAbortable, AbortError, createRateLimiter } from './rxjs_utils'; +const RATE_LIMIT_MAX_DELAY_MS = 5 * 60 * 1000; // 5 minutes + function getInternalUserSOClient() { const fakeRequest = ({ headers: {}, @@ -135,11 +136,19 @@ export function agentCheckinStateNewActionsFactory() { const agentPolicies$ = new Map>(); const newActions$ = createNewActionsSharedObservable(); // Rx operators - const rateLimiter = createRateLimiter( + const pollingTimeoutMs = appContextService.getConfig()?.fleet.pollingRequestTimeout ?? 0; + const rateLimiterIntervalMs = appContextService.getConfig()?.fleet.agentPolicyRolloutRateLimitIntervalMs ?? - AGENT_POLICY_ROLLOUT_RATE_LIMIT_INTERVAL_MS, + AGENT_POLICY_ROLLOUT_RATE_LIMIT_INTERVAL_MS; + const rateLimiterRequestPerInterval = appContextService.getConfig()?.fleet.agentPolicyRolloutRateLimitRequestPerInterval ?? - AGENT_POLICY_ROLLOUT_RATE_LIMIT_REQUEST_PER_INTERVAL + AGENT_POLICY_ROLLOUT_RATE_LIMIT_REQUEST_PER_INTERVAL; + const rateLimiterMaxDelay = Math.min(RATE_LIMIT_MAX_DELAY_MS, pollingTimeoutMs); + + const rateLimiter = createRateLimiter( + rateLimiterIntervalMs, + rateLimiterRequestPerInterval, + rateLimiterMaxDelay ); async function subscribeToNewActions( @@ -162,7 +171,7 @@ export function agentCheckinStateNewActionsFactory() { const stream$ = agentPolicy$.pipe( timeout( // Set a timeout 3s before the real timeout to have a chance to respond an empty response before socket timeout - Math.max((appContextService.getConfig()?.fleet.pollingRequestTimeout ?? 0) - 3000, 3000) + Math.max(pollingTimeoutMs - 3000, 3000) ), filter( (action) => @@ -173,9 +182,9 @@ export function agentCheckinStateNewActionsFactory() { (!agent.policy_revision || action.policy_revision > agent.policy_revision) ), rateLimiter(), - mergeMap((policyAction) => createAgentActionFromPolicyAction(soClient, agent, policyAction)), + switchMap((policyAction) => createAgentActionFromPolicyAction(soClient, agent, policyAction)), merge(newActions$), - mergeMap(async (data) => { + switchMap(async (data) => { if (!data) { return; } diff --git a/x-pack/plugins/ingest_manager/server/services/agents/crud.ts b/x-pack/plugins/ingest_manager/server/services/agents/crud.ts index a57735e25ff7b..c941b0512e597 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/crud.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/crud.ts @@ -3,25 +3,37 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - import Boom from 'boom'; import { SavedObjectsClientContract } from 'src/core/server'; -import { - AGENT_SAVED_OBJECT_TYPE, - AGENT_EVENT_SAVED_OBJECT_TYPE, - AGENT_TYPE_EPHEMERAL, - AGENT_POLLING_THRESHOLD_MS, -} from '../../constants'; +import { AGENT_SAVED_OBJECT_TYPE, AGENT_EVENT_SAVED_OBJECT_TYPE } from '../../constants'; import { AgentSOAttributes, Agent, AgentEventSOAttributes, ListWithKuery } from '../../types'; +import { escapeSearchQueryPhrase, normalizeKuery, findAllSOs } from '../saved_object'; import { savedObjectToAgent } from './saved_objects'; -import { escapeSearchQueryPhrase } from '../saved_object'; + +const ACTIVE_AGENT_CONDITION = `${AGENT_SAVED_OBJECT_TYPE}.attributes.active:true`; +const INACTIVE_AGENT_CONDITION = `NOT (${ACTIVE_AGENT_CONDITION})`; + +function _joinFilters(filters: string[], operator = 'AND') { + return filters.reduce((acc: string | undefined, filter) => { + if (acc) { + return `${acc} ${operator} (${filter})`; + } + + return `(${filter})`; + }, undefined); +} export async function listAgents( soClient: SavedObjectsClientContract, options: ListWithKuery & { showInactive: boolean; } -) { +): Promise<{ + agents: Agent[]; + total: number; + page: number; + perPage: number; +}> { const { page = 1, perPage = 20, @@ -30,47 +42,86 @@ export async function listAgents( kuery, showInactive = false, } = options; - const filters = []; if (kuery && kuery !== '') { - // To ensure users dont need to know about SO data structure... - filters.push( - kuery.replace( - new RegExp(`${AGENT_SAVED_OBJECT_TYPE}\.`, 'g'), - `${AGENT_SAVED_OBJECT_TYPE}.attributes.` - ) - ); + filters.push(normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery)); } if (showInactive === false) { - const agentActiveCondition = `${AGENT_SAVED_OBJECT_TYPE}.attributes.active:true AND not ${AGENT_SAVED_OBJECT_TYPE}.attributes.type:${AGENT_TYPE_EPHEMERAL}`; - const recentlySeenEphemeralAgent = `${AGENT_SAVED_OBJECT_TYPE}.attributes.active:true AND ${AGENT_SAVED_OBJECT_TYPE}.attributes.type:${AGENT_TYPE_EPHEMERAL} AND ${AGENT_SAVED_OBJECT_TYPE}.attributes.last_checkin > ${ - Date.now() - 3 * AGENT_POLLING_THRESHOLD_MS - }`; - filters.push(`(${agentActiveCondition}) OR (${recentlySeenEphemeralAgent})`); + filters.push(ACTIVE_AGENT_CONDITION); } - // eslint-disable-next-line @typescript-eslint/naming-convention - const { saved_objects, total } = await soClient.find({ + const { saved_objects: agentSOs, total } = await soClient.find({ type: AGENT_SAVED_OBJECT_TYPE, + filter: _joinFilters(filters), sortField, sortOrder, page, perPage, - filter: _joinFilters(filters), }); - const agents: Agent[] = saved_objects.map(savedObjectToAgent); - return { - agents, + agents: agentSOs.map(savedObjectToAgent), total, page, perPage, }; } +export async function listAllAgents( + soClient: SavedObjectsClientContract, + options: Omit & { + showInactive: boolean; + } +): Promise<{ + agents: Agent[]; + total: number; +}> { + const { sortField = 'enrolled_at', sortOrder = 'desc', kuery, showInactive = false } = options; + const filters = []; + + if (kuery && kuery !== '') { + filters.push(normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery)); + } + + if (showInactive === false) { + filters.push(ACTIVE_AGENT_CONDITION); + } + + const { saved_objects: agentSOs, total } = await findAllSOs(soClient, { + type: AGENT_SAVED_OBJECT_TYPE, + kuery: _joinFilters(filters), + sortField, + sortOrder, + }); + + return { + agents: agentSOs.map(savedObjectToAgent), + total, + }; +} + +export async function countInactiveAgents( + soClient: SavedObjectsClientContract, + options: Pick +): Promise { + const { kuery } = options; + const filters = [INACTIVE_AGENT_CONDITION]; + + if (kuery && kuery !== '') { + filters.push(normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery)); + } + + const { total } = await soClient.find({ + type: AGENT_SAVED_OBJECT_TYPE, + filter: _joinFilters(filters), + perPage: 0, + }); + + return total; +} + export async function getAgent(soClient: SavedObjectsClientContract, agentId: string) { const agent = savedObjectToAgent( await soClient.get(AGENT_SAVED_OBJECT_TYPE, agentId) @@ -78,6 +129,17 @@ export async function getAgent(soClient: SavedObjectsClientContract, agentId: st return agent; } +export async function getAgents(soClient: SavedObjectsClientContract, agentIds: string[]) { + const agentSOs = await soClient.bulkGet( + agentIds.map((agentId) => ({ + id: agentId, + type: AGENT_SAVED_OBJECT_TYPE, + })) + ); + const agents = agentSOs.saved_objects.map(savedObjectToAgent); + return agents; +} + export async function getAgentByAccessAPIKeyId( soClient: SavedObjectsClientContract, accessAPIKeyId: string @@ -142,13 +204,3 @@ export async function deleteAgent(soClient: SavedObjectsClientContract, agentId: active: false, }); } - -function _joinFilters(filters: string[], operator = 'AND') { - return filters.reduce((acc: string | undefined, filter) => { - if (acc) { - return `${acc} ${operator} (${filter})`; - } - - return `(${filter})`; - }, undefined); -} diff --git a/x-pack/plugins/ingest_manager/server/services/agents/events.ts b/x-pack/plugins/ingest_manager/server/services/agents/events.ts index dfa599e4ffdfd..627fe4f231d3d 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/events.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/events.ts @@ -7,6 +7,7 @@ import { SavedObjectsClientContract } from 'src/core/server'; import { AGENT_EVENT_SAVED_OBJECT_TYPE } from '../../constants'; import { AgentEventSOAttributes, AgentEvent } from '../../types'; +import { normalizeKuery } from '../saved_object'; export async function getAgentEvents( soClient: SavedObjectsClientContract, @@ -23,12 +24,7 @@ export async function getAgentEvents( const { total, saved_objects } = await soClient.find({ type: AGENT_EVENT_SAVED_OBJECT_TYPE, filter: - kuery && kuery !== '' - ? kuery.replace( - new RegExp(`${AGENT_EVENT_SAVED_OBJECT_TYPE}\.`, 'g'), - `${AGENT_EVENT_SAVED_OBJECT_TYPE}.attributes.` - ) - : undefined, + kuery && kuery !== '' ? normalizeKuery(AGENT_EVENT_SAVED_OBJECT_TYPE, kuery) : undefined, perPage, page, sortField: 'timestamp', diff --git a/x-pack/plugins/ingest_manager/server/services/agents/index.ts b/x-pack/plugins/ingest_manager/server/services/agents/index.ts index 400c099af4e93..c878b666bde88 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/index.ts @@ -9,6 +9,7 @@ export * from './events'; export * from './checkin'; export * from './enroll'; export * from './unenroll'; +export * from './upgrade'; export * from './status'; export * from './crud'; export * from './update'; diff --git a/x-pack/plugins/ingest_manager/server/services/agents/reassign.ts b/x-pack/plugins/ingest_manager/server/services/agents/reassign.ts index 3075e146093e3..345c07511f032 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/reassign.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/reassign.ts @@ -9,6 +9,7 @@ import Boom from 'boom'; import { AGENT_SAVED_OBJECT_TYPE } from '../../constants'; import { AgentSOAttributes } from '../../types'; import { agentPolicyService } from '../agent_policy'; +import { getAgents, listAllAgents } from './crud'; export async function reassignAgent( soClient: SavedObjectsClientContract, @@ -25,3 +26,44 @@ export async function reassignAgent( policy_revision: null, }); } + +export async function reassignAgents( + soClient: SavedObjectsClientContract, + options: + | { + agentIds: string[]; + } + | { + kuery: string; + }, + newAgentPolicyId: string +) { + const agentPolicy = await agentPolicyService.get(soClient, newAgentPolicyId); + if (!agentPolicy) { + throw Boom.notFound(`Agent policy not found: ${newAgentPolicyId}`); + } + + // Filter to agents that do not already use the new agent policy ID + const agents = + 'agentIds' in options + ? await getAgents(soClient, options.agentIds) + : ( + await listAllAgents(soClient, { + kuery: options.kuery, + showInactive: false, + }) + ).agents; + const agentsToUpdate = agents.filter((agent) => agent.policy_id !== newAgentPolicyId); + + // Update the necessary agents + return await soClient.bulkUpdate( + agentsToUpdate.map((agent) => ({ + type: AGENT_SAVED_OBJECT_TYPE, + id: agent.id, + attributes: { + policy_id: newAgentPolicyId, + policy_revision: null, + }, + })) + ); +} diff --git a/x-pack/plugins/ingest_manager/server/services/agents/unenroll.ts b/x-pack/plugins/ingest_manager/server/services/agents/unenroll.ts index e0ac2620cafd3..60533e1285141 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/unenroll.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/unenroll.ts @@ -3,13 +3,14 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import { chunk } from 'lodash'; import { SavedObjectsClientContract } from 'src/core/server'; import { AgentSOAttributes } from '../../types'; import { AGENT_SAVED_OBJECT_TYPE } from '../../constants'; import { getAgent } from './crud'; import * as APIKeyService from '../api_keys'; -import { createAgentAction } from './actions'; +import { createAgentAction, bulkCreateAgentActions } from './actions'; +import { getAgents, listAllAgents } from './crud'; export async function unenrollAgent(soClient: SavedObjectsClientContract, agentId: string) { const now = new Date().toISOString(); @@ -23,6 +24,53 @@ export async function unenrollAgent(soClient: SavedObjectsClientContract, agentI }); } +export async function unenrollAgents( + soClient: SavedObjectsClientContract, + options: + | { + agentIds: string[]; + } + | { + kuery: string; + } +) { + // Filter to agents that do not already unenrolled, or unenrolling + const agents = + 'agentIds' in options + ? await getAgents(soClient, options.agentIds) + : ( + await listAllAgents(soClient, { + kuery: options.kuery, + showInactive: false, + }) + ).agents; + const agentsToUpdate = agents.filter( + (agent) => !agent.unenrollment_started_at && !agent.unenrolled_at + ); + const now = new Date().toISOString(); + + // Create unenroll action for each agent + await bulkCreateAgentActions( + soClient, + agentsToUpdate.map((agent) => ({ + agent_id: agent.id, + created_at: now, + type: 'UNENROLL', + })) + ); + + // Update the necessary agents + return await soClient.bulkUpdate( + agentsToUpdate.map((agent) => ({ + type: AGENT_SAVED_OBJECT_TYPE, + id: agent.id, + attributes: { + unenrollment_started_at: now, + }, + })) + ); +} + export async function forceUnenrollAgent(soClient: SavedObjectsClientContract, agentId: string) { const agent = await getAgent(soClient, agentId); @@ -40,3 +88,63 @@ export async function forceUnenrollAgent(soClient: SavedObjectsClientContract, a unenrolled_at: new Date().toISOString(), }); } + +export async function forceUnenrollAgents( + soClient: SavedObjectsClientContract, + options: + | { + agentIds: string[]; + } + | { + kuery: string; + } +) { + // Filter to agents that are not already unenrolled + const agents = + 'agentIds' in options + ? await getAgents(soClient, options.agentIds) + : ( + await listAllAgents(soClient, { + kuery: options.kuery, + showInactive: false, + }) + ).agents; + const agentsToUpdate = agents.filter((agent) => !agent.unenrolled_at); + const now = new Date().toISOString(); + const apiKeys: string[] = []; + + // Get all API keys that need to be invalidated + agentsToUpdate.forEach((agent) => { + if (agent.access_api_key_id) { + apiKeys.push(agent.access_api_key_id); + } + if (agent.default_api_key_id) { + apiKeys.push(agent.default_api_key_id); + } + }); + + // Invalidate all API keys + // ES doesn't provide a bulk invalidate API, so this could take a long time depending on + // number of keys to invalidate. We run these in batches to avoid overloading ES. + if (apiKeys.length) { + const BATCH_SIZE = 500; + const batches = chunk(apiKeys, BATCH_SIZE); + for (const apiKeysBatch of batches) { + await Promise.all( + apiKeysBatch.map((apiKey) => APIKeyService.invalidateAPIKey(soClient, apiKey)) + ); + } + } + + // Update the necessary agents + return await soClient.bulkUpdate( + agentsToUpdate.map((agent) => ({ + type: AGENT_SAVED_OBJECT_TYPE, + id: agent.id, + attributes: { + active: false, + unenrolled_at: now, + }, + })) + ); +} diff --git a/x-pack/plugins/ingest_manager/server/services/agents/upgrade.ts b/x-pack/plugins/ingest_manager/server/services/agents/upgrade.ts new file mode 100644 index 0000000000000..cee3bc69f25db --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/services/agents/upgrade.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectsClientContract } from 'src/core/server'; +import { AgentSOAttributes, AgentAction, AgentActionSOAttributes } from '../../types'; +import { AGENT_ACTION_SAVED_OBJECT_TYPE, AGENT_SAVED_OBJECT_TYPE } from '../../constants'; +import { createAgentAction } from './actions'; + +export async function sendUpgradeAgentAction({ + soClient, + agentId, + version, + sourceUri, +}: { + soClient: SavedObjectsClientContract; + agentId: string; + version: string; + sourceUri: string; +}) { + const now = new Date().toISOString(); + const data = { + version, + source_uri: sourceUri, + }; + await createAgentAction(soClient, { + agent_id: agentId, + created_at: now, + data, + ack_data: data, + type: 'UPGRADE', + }); + await soClient.update(AGENT_SAVED_OBJECT_TYPE, agentId, { + upgraded_at: undefined, + upgrade_started_at: now, + }); +} + +export async function ackAgentUpgraded( + soClient: SavedObjectsClientContract, + agentAction: AgentAction +) { + const { + attributes: { ack_data: ackData }, + } = await soClient.get(AGENT_ACTION_SAVED_OBJECT_TYPE, agentAction.id); + if (!ackData) throw new Error('data missing from UPGRADE action'); + const { version } = JSON.parse(ackData); + if (!version) throw new Error('version missing from UPGRADE action'); + await soClient.update(AGENT_SAVED_OBJECT_TYPE, agentAction.agent_id, { + upgraded_at: new Date().toISOString(), + local_metadata: { + elastic: { + agent: { + version, + }, + }, + }, + }); +} diff --git a/x-pack/plugins/ingest_manager/server/services/api_keys/enrollment_api_key.ts b/x-pack/plugins/ingest_manager/server/services/api_keys/enrollment_api_key.ts index f058166fc2a4f..ea5d25dc9884f 100644 --- a/x-pack/plugins/ingest_manager/server/services/api_keys/enrollment_api_key.ts +++ b/x-pack/plugins/ingest_manager/server/services/api_keys/enrollment_api_key.ts @@ -12,6 +12,7 @@ import { ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE } from '../../constants'; import { createAPIKey, invalidateAPIKey } from './security'; import { agentPolicyService } from '../agent_policy'; import { appContextService } from '../app_context'; +import { normalizeKuery } from '../saved_object'; export async function listEnrollmentApiKeys( soClient: SavedObjectsClientContract, @@ -33,10 +34,7 @@ export async function listEnrollmentApiKeys( sortOrder: 'desc', filter: kuery && kuery !== '' - ? kuery.replace( - new RegExp(`${ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE}\.`, 'g'), - `${ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE}.attributes.` - ) + ? normalizeKuery(ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, kuery) : undefined, }); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts index 54b9c4d3fbb17..800151a41a429 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts @@ -6,6 +6,9 @@ import { SavedObject, SavedObjectsClientContract } from 'src/core/server'; import semver from 'semver'; +import Boom from 'boom'; +import { UnwrapPromise } from '@kbn/utility-types'; +import { BulkInstallPackageInfo, IBulkInstallPackageError } from '../../../../common'; import { PACKAGES_SAVED_OBJECT_TYPE, MAX_TIME_COMPLETE_INSTALL } from '../../../constants'; import { AssetReference, @@ -32,10 +35,15 @@ import { ArchiveAsset, } from '../kibana/assets/install'; import { updateCurrentWriteIndices } from '../elasticsearch/template/template'; -import { deleteKibanaSavedObjectsAssets } from './remove'; -import { PackageOutdatedError } from '../../../errors'; +import { deleteKibanaSavedObjectsAssets, removeInstallation } from './remove'; +import { + IngestManagerError, + PackageOutdatedError, + ingestErrorToResponseOptions, +} from '../../../errors'; import { getPackageSavedObjects } from './get'; import { installTransformForDataset } from '../elasticsearch/transform/install'; +import { appContextService } from '../../app_context'; export async function installLatestPackage(options: { savedObjectsClient: SavedObjectsClientContract; @@ -94,17 +102,185 @@ export async function ensureInstalledPackage(options: { return installation; } -export async function installPackage({ +export async function handleInstallPackageFailure({ savedObjectsClient, - pkgkey, + error, + pkgName, + pkgVersion, + installedPkg, callCluster, - force = false, }: { + savedObjectsClient: SavedObjectsClientContract; + error: IngestManagerError | Boom | Error; + pkgName: string; + pkgVersion: string; + installedPkg: SavedObject | undefined; + callCluster: CallESAsCurrentUser; +}) { + if (error instanceof IngestManagerError) { + return; + } + const logger = appContextService.getLogger(); + const pkgkey = Registry.pkgToPkgKey({ + name: pkgName, + version: pkgVersion, + }); + + // if there is an unknown server error, uninstall any package assets or reinstall the previous version if update + try { + const installType = getInstallType({ pkgVersion, installedPkg }); + if (installType === 'install' || installType === 'reinstall') { + logger.error(`uninstalling ${pkgkey} after error installing`); + await removeInstallation({ savedObjectsClient, pkgkey, callCluster }); + } + + if (installType === 'update') { + if (!installedPkg) { + logger.error( + `failed to rollback package after installation error ${error} because saved object was undefined` + ); + return; + } + const prevVersion = `${pkgName}-${installedPkg.attributes.version}`; + logger.error(`rolling back to ${prevVersion} after error installing ${pkgkey}`); + await installPackage({ + savedObjectsClient, + pkgkey: prevVersion, + callCluster, + }); + } + } catch (e) { + logger.error(`failed to uninstall or rollback package after installation error ${e}`); + } +} + +type BulkInstallResponse = BulkInstallPackageInfo | IBulkInstallPackageError; +function bulkInstallErrorToOptions({ + pkgToUpgrade, + error, +}: { + pkgToUpgrade: string; + error: Error; +}): IBulkInstallPackageError { + const { statusCode, body } = ingestErrorToResponseOptions(error); + return { + name: pkgToUpgrade, + statusCode, + error: body.message, + }; +} + +interface UpgradePackageParams { + savedObjectsClient: SavedObjectsClientContract; + callCluster: CallESAsCurrentUser; + installedPkg: UnwrapPromise>; + latestPkg: UnwrapPromise>; + pkgToUpgrade: string; +} +async function upgradePackage({ + savedObjectsClient, + callCluster, + installedPkg, + latestPkg, + pkgToUpgrade, +}: UpgradePackageParams): Promise { + if (!installedPkg || semver.gt(latestPkg.version, installedPkg.attributes.version)) { + const pkgkey = Registry.pkgToPkgKey({ + name: latestPkg.name, + version: latestPkg.version, + }); + + try { + const assets = await installPackage({ savedObjectsClient, pkgkey, callCluster }); + return { + name: pkgToUpgrade, + newVersion: latestPkg.version, + oldVersion: installedPkg?.attributes.version ?? null, + assets, + }; + } catch (installFailed) { + await handleInstallPackageFailure({ + savedObjectsClient, + error: installFailed, + pkgName: latestPkg.name, + pkgVersion: latestPkg.version, + installedPkg, + callCluster, + }); + return bulkInstallErrorToOptions({ pkgToUpgrade, error: installFailed }); + } + } else { + // package was already at the latest version + return { + name: pkgToUpgrade, + newVersion: latestPkg.version, + oldVersion: latestPkg.version, + assets: [ + ...installedPkg.attributes.installed_es, + ...installedPkg.attributes.installed_kibana, + ], + }; + } +} + +interface BulkInstallPackagesParams { + savedObjectsClient: SavedObjectsClientContract; + packagesToUpgrade: string[]; + callCluster: CallESAsCurrentUser; +} +export async function bulkInstallPackages({ + savedObjectsClient, + packagesToUpgrade, + callCluster, +}: BulkInstallPackagesParams): Promise { + const installedAndLatestPromises = packagesToUpgrade.map((pkgToUpgrade) => + Promise.all([ + getInstallationObject({ savedObjectsClient, pkgName: pkgToUpgrade }), + Registry.fetchFindLatestPackage(pkgToUpgrade), + ]) + ); + const installedAndLatestResults = await Promise.allSettled(installedAndLatestPromises); + const installResponsePromises = installedAndLatestResults.map(async (result, index) => { + const pkgToUpgrade = packagesToUpgrade[index]; + if (result.status === 'fulfilled') { + const [installedPkg, latestPkg] = result.value; + return upgradePackage({ + savedObjectsClient, + callCluster, + installedPkg, + latestPkg, + pkgToUpgrade, + }); + } else { + return bulkInstallErrorToOptions({ pkgToUpgrade, error: result.reason }); + } + }); + const installResults = await Promise.allSettled(installResponsePromises); + const installResponses = installResults.map((result, index) => { + const pkgToUpgrade = packagesToUpgrade[index]; + if (result.status === 'fulfilled') { + return result.value; + } else { + return bulkInstallErrorToOptions({ pkgToUpgrade, error: result.reason }); + } + }); + + return installResponses; +} + +interface InstallPackageParams { savedObjectsClient: SavedObjectsClientContract; pkgkey: string; callCluster: CallESAsCurrentUser; force?: boolean; -}): Promise { +} + +export async function installPackage({ + savedObjectsClient, + pkgkey, + callCluster, + force = false, +}: InstallPackageParams): Promise { // TODO: change epm API to /packageName/version so we don't need to do this const { pkgName, pkgVersion } = Registry.splitPkgKey(pkgkey); // TODO: calls to getInstallationObject, Registry.fetchInfo, and Registry.fetchFindLatestPackge diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/registry_url.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/registry_url.ts index b788d1bcbb4a9..6618220a27085 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/registry_url.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/registry_url.ts @@ -29,9 +29,8 @@ const getDefaultRegistryUrl = (): string => { }; export const getRegistryUrl = (): string => { - const license = licenseService.getLicenseInformation(); const customUrl = appContextService.getConfig()?.registryUrl; - const isGoldPlus = license?.isAvailable && license?.isActive && license?.hasAtLeast('gold'); + const isGoldPlus = licenseService.isGoldPlus(); if (customUrl && isGoldPlus) { return customUrl; diff --git a/x-pack/plugins/ingest_manager/server/services/index.ts b/x-pack/plugins/ingest_manager/server/services/index.ts index 5942277e90824..7a62c307973c2 100644 --- a/x-pack/plugins/ingest_manager/server/services/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/index.ts @@ -7,6 +7,7 @@ import { SavedObjectsClientContract, KibanaRequest } from 'kibana/server'; import { AgentStatus, Agent, EsAssetReference } from '../types'; import * as settingsService from './settings'; +import { getAgent, listAgents } from './agents'; export { ESIndexPatternSavedObjectService } from './es_index_pattern'; export { getRegistryUrl } from './epm/registry/registry_url'; @@ -40,7 +41,7 @@ export interface AgentService { /** * Get an Agent by id */ - getAgent(soClient: SavedObjectsClientContract, agentId: string): Promise; + getAgent: typeof getAgent; /** * Authenticate an agent with access toekn */ @@ -55,20 +56,7 @@ export interface AgentService { /** * List agents */ - listAgents( - soClient: SavedObjectsClientContract, - options: { - page: number; - perPage: number; - kuery?: string; - showInactive: boolean; - } - ): Promise<{ - agents: Agent[]; - total: number; - page: number; - perPage: number; - }>; + listAgents: typeof listAgents; } // Saved object services diff --git a/x-pack/plugins/ingest_manager/server/services/license.ts b/x-pack/plugins/ingest_manager/server/services/license.ts index bd96dbc7e3aff..a67ec9880ec09 100644 --- a/x-pack/plugins/ingest_manager/server/services/license.ts +++ b/x-pack/plugins/ingest_manager/server/services/license.ts @@ -3,36 +3,6 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Observable, Subscription } from 'rxjs'; -import { ILicense } from '../../../licensing/server'; - -class LicenseService { - private observable: Observable | null = null; - private subscription: Subscription | null = null; - private licenseInformation: ILicense | null = null; - - private updateInformation(licenseInformation: ILicense) { - this.licenseInformation = licenseInformation; - } - - public start(license$: Observable) { - this.observable = license$; - this.subscription = this.observable.subscribe(this.updateInformation.bind(this)); - } - - public stop() { - if (this.subscription) { - this.subscription.unsubscribe(); - } - } - - public getLicenseInformation() { - return this.licenseInformation; - } - - public getLicenseInformation$() { - return this.observable; - } -} +import { LicenseService } from '../../common'; export const licenseService = new LicenseService(); diff --git a/x-pack/plugins/ingest_manager/server/services/package_policy.ts b/x-pack/plugins/ingest_manager/server/services/package_policy.ts index b7e1806979db8..3a02544250ff0 100644 --- a/x-pack/plugins/ingest_manager/server/services/package_policy.ts +++ b/x-pack/plugins/ingest_manager/server/services/package_policy.ts @@ -30,6 +30,7 @@ import * as Registry from './epm/registry'; import { getPackageInfo, getInstallation, ensureInstalledPackage } from './epm/packages'; import { getAssetsData } from './epm/packages/assets'; import { createStream } from './epm/agent/agent'; +import { normalizeKuery } from './saved_object'; const SAVED_OBJECT_TYPE = PACKAGE_POLICY_SAVED_OBJECT_TYPE; @@ -211,13 +212,7 @@ class PackagePolicyService { sortOrder, page, perPage, - // To ensure users don't need to know about SO data structure... - filter: kuery - ? kuery.replace( - new RegExp(`${SAVED_OBJECT_TYPE}\.`, 'g'), - `${SAVED_OBJECT_TYPE}.attributes.` - ) - : undefined, + filter: kuery ? normalizeKuery(SAVED_OBJECT_TYPE, kuery) : undefined, }); return { diff --git a/x-pack/plugins/ingest_manager/server/services/saved_object.ts b/x-pack/plugins/ingest_manager/server/services/saved_object.ts index 8fe7ffcdfc896..06772206d5198 100644 --- a/x-pack/plugins/ingest_manager/server/services/saved_object.ts +++ b/x-pack/plugins/ingest_manager/server/services/saved_object.ts @@ -3,6 +3,8 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { SavedObjectsClientContract, SavedObjectsFindResponse } from 'src/core/server'; +import { ListWithKuery } from '../types'; /** * Escape a value with double quote to use with saved object search @@ -12,3 +14,70 @@ export function escapeSearchQueryPhrase(val: string): string { return `"${val.replace(/["]/g, '"')}"`; } + +// Adds `.attribute` to any kuery strings that are missing it, this comes from +// internal SO structure. Kuery strings that come from UI will typicall have +// `.attribute` hidden to simplify UX, so this normalizes any kuery string for +// filtering SOs +export const normalizeKuery = (savedObjectType: string, kuery: string): string => { + return kuery.replace( + new RegExp(`${savedObjectType}\.(?!attributes\.)`, 'g'), + `${savedObjectType}.attributes.` + ); +}; + +// Like saved object client `.find()`, but ignores `page` and `perPage` parameters and +// returns *all* matching saved objects by collocating results from all `.find` pages. +// This function actually doesn't offer any additional benefits over `.find()` for now +// due to SO client limitations (see comments below), so is a placeholder for when SO +// client is improved. +export const findAllSOs = async ( + soClient: SavedObjectsClientContract, + options: Omit & { + type: string; + } +): Promise, 'saved_objects' | 'total'>> => { + const { type, sortField, sortOrder, kuery } = options; + let savedObjectResults: SavedObjectsFindResponse['saved_objects'] = []; + + // TODO: This is the default `index.max_result_window` ES setting, which dictates + // the maximum amount of results allowed to be returned from a search. It's possible + // for the actual setting to differ from the default. Can we retrieve the real + // setting in the future? + const searchLimit = 10000; + + const query = { + type, + sortField, + sortOrder, + filter: kuery, + page: 1, + perPage: searchLimit, + }; + + const { saved_objects: initialSOs, total } = await soClient.find(query); + + savedObjectResults = initialSOs; + + // The saved object client can't actually page through more than the first 10,000 + // results, due to the same `index.max_result_window` constraint. The commented out + // code below is an example of paging through rest of results when the SO client + // offers that kind of support. + // if (total > searchLimit) { + // const remainingPages = Math.ceil((total - searchLimit) / searchLimit); + // for (let currentPage = 2; currentPage <= remainingPages + 1; currentPage++) { + // const { saved_objects: currentPageSavedObjects } = await soClient.find({ + // ...query, + // page: currentPage, + // }); + // if (currentPageSavedObjects.length) { + // savedObjectResults = savedObjectResults.concat(currentPageSavedObjects); + // } + // } + // } + + return { + saved_objects: savedObjectResults, + total, + }; +}; diff --git a/x-pack/plugins/ingest_manager/server/types/models/agent.ts b/x-pack/plugins/ingest_manager/server/types/models/agent.ts index b249705fe6c2f..15004e60a6fa4 100644 --- a/x-pack/plugins/ingest_manager/server/types/models/agent.ts +++ b/x-pack/plugins/ingest_manager/server/types/models/agent.ts @@ -62,7 +62,12 @@ export const AgentEventSchema = schema.object({ }); export const NewAgentActionSchema = schema.object({ - type: schema.oneOf([schema.literal('CONFIG_CHANGE'), schema.literal('UNENROLL')]), + type: schema.oneOf([ + schema.literal('CONFIG_CHANGE'), + schema.literal('UNENROLL'), + schema.literal('UPGRADE'), + ]), data: schema.maybe(schema.any()), + ack_data: schema.maybe(schema.any()), sent_at: schema.maybe(schema.string()), }); diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/agent.ts b/x-pack/plugins/ingest_manager/server/types/rest_spec/agent.ts index 43ee0c89126e9..3866ef095563e 100644 --- a/x-pack/plugins/ingest_manager/server/types/rest_spec/agent.ts +++ b/x-pack/plugins/ingest_manager/server/types/rest_spec/agent.ts @@ -172,6 +172,23 @@ export const PostAgentUnenrollRequestSchema = { ), }; +export const PostAgentUpgradeRequestSchema = { + params: schema.object({ + agentId: schema.string(), + }), + body: schema.object({ + source_uri: schema.string(), + version: schema.string(), + }), +}; + +export const PostBulkAgentUnenrollRequestSchema = { + body: schema.object({ + agents: schema.oneOf([schema.arrayOf(schema.string()), schema.string()]), + force: schema.maybe(schema.boolean()), + }), +}; + export const PutAgentReassignRequestSchema = { params: schema.object({ agentId: schema.string(), @@ -181,6 +198,13 @@ export const PutAgentReassignRequestSchema = { }), }; +export const PostBulkAgentReassignRequestSchema = { + body: schema.object({ + policy_id: schema.string(), + agents: schema.oneOf([schema.arrayOf(schema.string()), schema.string()]), + }), +}; + export const GetOneAgentEventsRequestSchema = { params: schema.object({ agentId: schema.string(), diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/epm.ts b/x-pack/plugins/ingest_manager/server/types/rest_spec/epm.ts index d7a801feec34f..5d2a078374854 100644 --- a/x-pack/plugins/ingest_manager/server/types/rest_spec/epm.ts +++ b/x-pack/plugins/ingest_manager/server/types/rest_spec/epm.ts @@ -43,6 +43,12 @@ export const InstallPackageFromRegistryRequestSchema = { ), }; +export const BulkUpgradePackagesFromRegistryRequestSchema = { + body: schema.object({ + packages: schema.arrayOf(schema.string(), { minSize: 1 }), + }), +}; + export const InstallPackageByUploadRequestSchema = { body: schema.buffer(), }; diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_clone.helpers.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_clone.helpers.ts index f369bfe66f642..2559b93bd606d 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_clone.helpers.ts +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_clone.helpers.ts @@ -5,10 +5,10 @@ */ import { registerTestBed, TestBedConfig, TestBed } from '../../../../../test_utils'; -import { BASE_PATH } from '../../../common/constants'; import { PipelinesClone } from '../../../public/application/sections/pipelines_clone'; import { getFormActions, PipelineFormTestSubjects } from './pipeline_form.helpers'; import { WithAppDependencies } from './setup_environment'; +import { getClonePath, ROUTES } from '../../../public/application/services/navigation'; export type PipelinesCloneTestBed = TestBed & { actions: ReturnType; @@ -29,8 +29,8 @@ export const PIPELINE_TO_CLONE = { const testBedConfig: TestBedConfig = { memoryRouter: { - initialEntries: [`${BASE_PATH}create/${PIPELINE_TO_CLONE.name}`], - componentRoutePath: `${BASE_PATH}create/:name`, + initialEntries: [getClonePath({ clonedPipelineName: PIPELINE_TO_CLONE.name })], + componentRoutePath: ROUTES.clone, }, doMountAsync: true, }; diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_create.helpers.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_create.helpers.ts index ce5ab1faa01be..22f68f12804d6 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_create.helpers.ts +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_create.helpers.ts @@ -5,10 +5,10 @@ */ import { registerTestBed, TestBedConfig, TestBed } from '../../../../../test_utils'; -import { BASE_PATH } from '../../../common/constants'; import { PipelinesCreate } from '../../../public/application/sections/pipelines_create'; import { getFormActions, PipelineFormTestSubjects } from './pipeline_form.helpers'; import { WithAppDependencies } from './setup_environment'; +import { getCreatePath, ROUTES } from '../../../public/application/services/navigation'; export type PipelinesCreateTestBed = TestBed & { actions: ReturnType; @@ -16,8 +16,8 @@ export type PipelinesCreateTestBed = TestBed & { const testBedConfig: TestBedConfig = { memoryRouter: { - initialEntries: [`${BASE_PATH}/create`], - componentRoutePath: `${BASE_PATH}/create`, + initialEntries: [getCreatePath()], + componentRoutePath: ROUTES.create, }, doMountAsync: true, }; diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_edit.helpers.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_edit.helpers.ts index 31c9630086178..5e0739f78eecd 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_edit.helpers.ts +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_edit.helpers.ts @@ -5,10 +5,10 @@ */ import { registerTestBed, TestBedConfig, TestBed } from '../../../../../test_utils'; -import { BASE_PATH } from '../../../common/constants'; import { PipelinesEdit } from '../../../public/application/sections/pipelines_edit'; import { getFormActions, PipelineFormTestSubjects } from './pipeline_form.helpers'; import { WithAppDependencies } from './setup_environment'; +import { getEditPath, ROUTES } from '../../../public/application/services/navigation'; export type PipelinesEditTestBed = TestBed & { actions: ReturnType; @@ -29,8 +29,8 @@ export const PIPELINE_TO_EDIT = { const testBedConfig: TestBedConfig = { memoryRouter: { - initialEntries: [`${BASE_PATH}edit/${PIPELINE_TO_EDIT.name}`], - componentRoutePath: `${BASE_PATH}edit/:name`, + initialEntries: [getEditPath({ pipelineName: PIPELINE_TO_EDIT.name })], + componentRoutePath: ROUTES.edit, }, doMountAsync: true, }; diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_list.helpers.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_list.helpers.ts index 03ffe361bb5a6..43ca849e61aee 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_list.helpers.ts +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_list.helpers.ts @@ -6,7 +6,6 @@ import { act } from 'react-dom/test-utils'; -import { BASE_PATH } from '../../../common/constants'; import { registerTestBed, TestBed, @@ -16,11 +15,12 @@ import { } from '../../../../../test_utils'; import { PipelinesList } from '../../../public/application/sections/pipelines_list'; import { WithAppDependencies } from './setup_environment'; +import { getListPath, ROUTES } from '../../../public/application/services/navigation'; const testBedConfig: TestBedConfig = { memoryRouter: { - initialEntries: [BASE_PATH], - componentRoutePath: BASE_PATH, + initialEntries: [getListPath()], + componentRoutePath: ROUTES.list, }, doMountAsync: true, }; diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_create.test.tsx b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_create.test.tsx index 6074c64d2bdb0..18ca71f2bb73a 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_create.test.tsx +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_create.test.tsx @@ -185,30 +185,5 @@ describe('', () => { expect(find('savePipelineError').find('li').length).toBe(8); }); }); - - describe('test pipeline', () => { - beforeEach(async () => { - await act(async () => { - testBed = await setup(); - - const { waitFor } = testBed; - - await waitFor('pipelineForm'); - }); - }); - - test('should open the test pipeline flyout', async () => { - const { actions, exists, find, waitFor } = testBed; - - await act(async () => { - actions.clickAddDocumentsButton(); - await waitFor('testPipelineFlyout'); - }); - - // Verify test pipeline flyout opens - expect(exists('testPipelineFlyout')).toBe(true); - expect(find('testPipelineFlyout.title').text()).toBe('Test pipeline'); - }); - }); }); }); diff --git a/x-pack/plugins/ingest_pipelines/common/constants.ts b/x-pack/plugins/ingest_pipelines/common/constants.ts index 4c6c6fefaad83..0d6f977bfbfed 100644 --- a/x-pack/plugins/ingest_pipelines/common/constants.ts +++ b/x-pack/plugins/ingest_pipelines/common/constants.ts @@ -9,9 +9,9 @@ const basicLicense: LicenseType = 'basic'; export const PLUGIN_ID = 'ingest_pipelines'; -export const PLUGIN_MIN_LICENSE_TYPE = basicLicense; +export const MANAGEMENT_APP_ID = 'management'; -export const BASE_PATH = '/'; +export const PLUGIN_MIN_LICENSE_TYPE = basicLicense; export const API_BASE_PATH = '/api/ingest_pipelines'; diff --git a/x-pack/plugins/ingest_pipelines/kibana.json b/x-pack/plugins/ingest_pipelines/kibana.json index 38d28fbba20b4..2fe87c5e7a162 100644 --- a/x-pack/plugins/ingest_pipelines/kibana.json +++ b/x-pack/plugins/ingest_pipelines/kibana.json @@ -3,7 +3,7 @@ "version": "8.0.0", "server": true, "ui": true, - "requiredPlugins": ["licensing", "management", "features"], + "requiredPlugins": ["licensing", "management", "features", "share"], "optionalPlugins": ["security", "usageCollection"], "configPath": ["xpack", "ingest_pipelines"], "requiredBundles": ["esUiShared", "kibanaReact"] diff --git a/x-pack/plugins/ingest_pipelines/public/application/app.tsx b/x-pack/plugins/ingest_pipelines/public/application/app.tsx index 55b59caab8d60..e78c4d3983183 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/app.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/app.tsx @@ -21,13 +21,14 @@ import { } from '../shared_imports'; import { PipelinesList, PipelinesCreate, PipelinesEdit, PipelinesClone } from './sections'; +import { ROUTES } from './services/navigation'; export const AppWithoutRouter = () => ( - - - - + + + + {/* Catch all */} diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx index 5279bd718c16e..ffd82b0bbaf35 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx @@ -11,8 +11,6 @@ import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiSpacer } from import { useForm, Form, FormConfig } from '../../../shared_imports'; import { Pipeline, Processor } from '../../../../common/types'; -import './pipeline_form.scss'; - import { OnUpdateHandlerArg, OnUpdateHandler } from '../pipeline_processors_editor'; import { PipelineRequestFlyout } from './pipeline_request_flyout'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form_fields.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form_fields.tsx index 6033f34af6825..a7ffe7ba02caa 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form_fields.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form_fields.tsx @@ -6,7 +6,7 @@ import React, { useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiSpacer, EuiSwitch, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiSpacer, EuiSwitch } from '@elastic/eui'; import { Processor } from '../../../../common/types'; @@ -14,15 +14,11 @@ import { getUseField, getFormRow, Field } from '../../../shared_imports'; import { ProcessorsEditorContextProvider, - GlobalOnFailureProcessorsEditor, - ProcessorsEditor, OnUpdateHandler, OnDoneLoadJsonHandler, + PipelineProcessorsEditor, } from '../pipeline_processors_editor'; -import { ProcessorsHeader } from './processors_header'; -import { OnFailureProcessorsTitle } from './on_failure_processors_title'; - interface Props { processors: Processor[]; onFailure?: Processor[]; @@ -118,28 +114,12 @@ export const PipelineFormFields: React.FunctionComponent = ({ {/* Pipeline Processors Editor */} - -
- - - - - - - - - - - - - - -
+
); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx index e46e5156e30f3..10fb73df1ce1c 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx @@ -11,12 +11,7 @@ import { notificationServiceMock, scopedHistoryMock } from 'src/core/public/mock import { LocationDescriptorObject } from 'history'; import { KibanaContextProvider } from 'src/plugins/kibana_react/public'; import { registerTestBed, TestBed } from '../../../../../../../test_utils'; -import { - ProcessorsEditorContextProvider, - Props, - ProcessorsEditor, - GlobalOnFailureProcessorsEditor, -} from '../'; +import { ProcessorsEditorContextProvider, Props, PipelineProcessorsEditor } from '../'; import { breadcrumbService, @@ -90,7 +85,7 @@ const testBedSetup = registerTestBed( (props: Props) => ( - + ), @@ -210,4 +205,5 @@ type TestSubject = | 'processorSettingsFormFlyout' | 'processorTypeSelector' | 'pipelineEditorOnFailureTree' + | 'processorsEmptyPrompt' | string; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx index 74ae8b8894b9f..b80d238362118 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx @@ -55,6 +55,23 @@ describe('Pipeline Editor', () => { expect(arg.getData()).toEqual(testProcessors); }); + describe('no processors', () => { + beforeEach(async () => { + testBed = await setup({ + value: { + processors: [], + }, + onFlyoutOpen: jest.fn(), + onUpdate, + }); + }); + + it('displays an empty prompt if no processors are defined', () => { + const { exists } = testBed; + expect(exists('processorsEmptyPrompt')).toBe(true); + }); + }); + describe('processors', () => { it('adds a new processor', async () => { const { actions } = testBed; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/add_processor_button.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/add_processor_button.tsx index 4aabcc1d59d73..03b497320dfbc 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/add_processor_button.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/add_processor_button.tsx @@ -6,30 +6,49 @@ import React, { FunctionComponent } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiButtonEmpty } from '@elastic/eui'; +import { EuiButtonEmpty, EuiButton } from '@elastic/eui'; import { usePipelineProcessorsContext } from '../context'; export interface Props { onClick: () => void; + renderButtonAsLink?: boolean; } +const addProcessorButtonLabel = i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.addProcessorButtonLabel', + { + defaultMessage: 'Add a processor', + } +); + export const AddProcessorButton: FunctionComponent = (props) => { - const { onClick } = props; + const { onClick, renderButtonAsLink } = props; const { state: { editor }, } = usePipelineProcessorsContext(); + + if (renderButtonAsLink) { + return ( + + {addProcessorButtonLabel} + + ); + } + return ( - - {i18n.translate('xpack.ingestPipelines.pipelineEditor.addProcessorButtonLabel', { - defaultMessage: 'Add a processor', - })} - + {addProcessorButtonLabel} +
); }; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/index.ts index d476202aa43bb..2e62a81ffa153 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/index.ts @@ -19,3 +19,9 @@ export { OnDoneLoadJsonHandler, LoadFromJsonButton } from './load_from_json'; export { TestPipelineActions } from './test_pipeline'; export { PipelineProcessorsItemTooltip, Position } from './pipeline_processors_editor_item_tooltip'; + +export { ProcessorsEmptyPrompt } from './processors_empty_prompt'; + +export { ProcessorsHeader } from './processors_header'; + +export { OnFailureProcessorsTitle } from './on_failure_processors_title'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/load_from_json/button.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/load_from_json/button.tsx index 21d15fc86a0ce..38700d6a7a87c 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/load_from_json/button.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/load_from_json/button.tsx @@ -15,7 +15,7 @@ interface Props { const i18nTexts = { buttonLabel: i18n.translate('xpack.ingestPipelines.pipelineEditor.loadFromJson.buttonLabel', { - defaultMessage: 'Import', + defaultMessage: 'Import processors', }), }; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/on_failure_processors_title.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/on_failure_processors_title.tsx similarity index 96% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/on_failure_processors_title.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/on_failure_processors_title.tsx index 0beb5657b54cb..7adc37d1897d1 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/on_failure_processors_title.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/on_failure_processors_title.tsx @@ -8,7 +8,7 @@ import React, { FunctionComponent } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useKibana } from '../../../shared_imports'; +import { useKibana } from '../../../../shared_imports'; export const OnFailureProcessorsTitle: FunctionComponent = () => { const { services } = useKibana(); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_form.container.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_form.container.tsx index 332908d0756f2..c3b1799ac2a28 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_form.container.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_form.container.tsx @@ -113,15 +113,15 @@ export const ProcessorFormContainer: FunctionComponent = ({ handleSubmit={handleSubmit} /> ); - } else { - return ( - - ); } + + return ( + + ); }; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_empty_prompt.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_empty_prompt.tsx new file mode 100644 index 0000000000000..3750ddda25d10 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_empty_prompt.tsx @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiEmptyPrompt, EuiSpacer, EuiLink } from '@elastic/eui'; +import { useKibana } from '../../../../shared_imports'; +import { usePipelineProcessorsContext } from '../context'; +import { AddProcessorButton } from './add_processor_button'; +import { OnDoneLoadJsonHandler, LoadFromJsonButton } from './load_from_json'; + +const i18nTexts = { + emptyPromptTitle: i18n.translate('xpack.ingestPipelines.pipelineEditor.emptyPrompt.title', { + defaultMessage: 'Add your first processor', + }), +}; + +export interface Props { + onLoadJson: OnDoneLoadJsonHandler; +} + +export const ProcessorsEmptyPrompt: FunctionComponent = ({ onLoadJson }) => { + const { onTreeAction } = usePipelineProcessorsContext(); + const { services } = useKibana(); + + return ( + {i18nTexts.emptyPromptTitle}} + data-test-subj="processorsEmptyPrompt" + body={ +

+ + {i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.processorsDocumentationLink', + { + defaultMessage: 'Learn more.', + } + )} + + ), + }} + /> +

+ } + actions={ + <> + { + onTreeAction({ type: 'addProcessor', payload: { target: ['processors'] } }); + }} + /> + + + + + + } + /> + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/processors_header.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_header.tsx similarity index 78% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/processors_header.tsx rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_header.tsx index 43477affa8d94..24f3207d6bea4 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/processors_header.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_header.tsx @@ -9,21 +9,32 @@ import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiText, EuiTitle } from '@elastic/ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useKibana } from '../../../shared_imports'; +import { useKibana } from '../../../../shared_imports'; -import { - LoadFromJsonButton, - OnDoneLoadJsonHandler, - TestPipelineActions, -} from '../pipeline_processors_editor'; +import { LoadFromJsonButton, OnDoneLoadJsonHandler, TestPipelineActions } from './'; export interface Props { onLoadJson: OnDoneLoadJsonHandler; + hasProcessors: boolean; } -export const ProcessorsHeader: FunctionComponent = ({ onLoadJson }) => { +export const ProcessorsHeader: FunctionComponent = ({ onLoadJson, hasProcessors }) => { const { services } = useKibana(); + const ProcessorTitle: FunctionComponent = () => ( + +

+ {i18n.translate('xpack.ingestPipelines.pipelineEditor.processorsTreeTitle', { + defaultMessage: 'Processors', + })} +

+
+ ); + + if (!hasProcessors) { + return ; + } + return ( = ({ onLoadJson }) => { - -

- {i18n.translate('xpack.ingestPipelines.pipelineEditor.processorsTreeTitle', { - defaultMessage: 'Processors', - })} -

-
+
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/tree_node.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/tree_node.tsx index e9008e6f5b693..3a8299c017d8d 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/tree_node.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/tree_node.tsx @@ -70,6 +70,7 @@ export const TreeNode: FunctionComponent = ({ /> onAction({ type: 'addProcessor', diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.tsx index 8b344a137f3a8..ffc0a1459b791 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.tsx @@ -99,7 +99,7 @@ export const ProcessorsTree: FunctionComponent = memo((props) => { - + {!processors.length && ( = memo((props) => { onClick={() => { onAction({ type: 'addProcessor', payload: { target: baseSelector } }); }} + renderButtonAsLink /> diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/index.ts index c462b19c79327..ca5184da25a07 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/index.ts @@ -15,3 +15,5 @@ export { OnUpdateHandlerArg, OnUpdateHandler } from './types'; export { SerializeResult } from './serialize'; export { LoadFromJsonButton, OnDoneLoadJsonHandler, TestPipelineActions } from './components'; + +export { PipelineProcessorsEditor } from './pipeline_processors_editor'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.scss b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/pipeline_processors_editor.scss similarity index 100% rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.scss rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/pipeline_processors_editor.scss diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/pipeline_processors_editor.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/pipeline_processors_editor.tsx new file mode 100644 index 0000000000000..beb165973d3cd --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/pipeline_processors_editor.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +import { usePipelineProcessorsContext } from './context'; +import { + ProcessorsEmptyPrompt, + OnFailureProcessorsTitle, + ProcessorsHeader, + OnDoneLoadJsonHandler, +} from './components'; +import { ProcessorsEditor, GlobalOnFailureProcessorsEditor } from './editors'; + +import './pipeline_processors_editor.scss'; + +interface Props { + onLoadJson: OnDoneLoadJsonHandler; +} + +export const PipelineProcessorsEditor: React.FunctionComponent = ({ onLoadJson }) => { + const { + state: { processors: allProcessors }, + } = usePipelineProcessorsContext(); + + const { + state: { processors, onFailure }, + } = allProcessors; + + const showEmptyPrompt = processors.length === 0 && onFailure.length === 0; + + let content: React.ReactNode; + + if (showEmptyPrompt) { + content = ; + } else { + content = ( + <> + + + + + + + + + ); + } + + return ( +
+ + + 0} /> + + + {content} + + +
+ ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx index acca1c4e03f40..d4aa11715248e 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_create/pipelines_create.tsx @@ -16,7 +16,7 @@ import { EuiSpacer, } from '@elastic/eui'; -import { BASE_PATH } from '../../../../common/constants'; +import { getListPath } from '../../services/navigation'; import { Pipeline } from '../../../../common/types'; import { useKibana } from '../../../shared_imports'; import { PipelineForm } from '../../components'; @@ -50,11 +50,11 @@ export const PipelinesCreate: React.FunctionComponent { - history.push(BASE_PATH); + history.push(getListPath()); }; useEffect(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx index e09cf4820771f..35ca1635ab9c3 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx @@ -17,11 +17,11 @@ import { } from '@elastic/eui'; import { EuiCallOut } from '@elastic/eui'; -import { BASE_PATH } from '../../../../common/constants'; import { Pipeline } from '../../../../common/types'; import { useKibana, SectionLoading } from '../../../shared_imports'; -import { PipelineForm } from '../../components'; +import { getListPath } from '../../services/navigation'; +import { PipelineForm } from '../../components'; import { attemptToURIDecode } from '../shared'; interface MatchParams { @@ -56,11 +56,11 @@ export const PipelinesEdit: React.FunctionComponent { - history.push(BASE_PATH); + history.push(getListPath()); }; useEffect(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx index eba69ff454911..7f4caa09b6df0 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx @@ -11,6 +11,7 @@ import { useHistory } from 'react-router-dom'; import { ScopedHistory } from 'kibana/public'; import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; import { useKibana } from '../../../shared_imports'; +import { getCreatePath } from '../../services/navigation'; export const EmptyList: FunctionComponent = () => { const { services } = useKibana(); @@ -44,7 +45,11 @@ export const EmptyList: FunctionComponent = () => {

} actions={ - + {i18n.translate('xpack.ingestPipelines.list.table.emptyPrompt.createButtonLabel', { defaultMessage: 'Create a pipeline', })} diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx index 88148f1bc5746..be31f86e30c27 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/main.tsx @@ -24,9 +24,9 @@ import { } from '@elastic/eui'; import { Pipeline } from '../../../../common/types'; -import { BASE_PATH } from '../../../../common/constants'; import { useKibana, SectionLoading } from '../../../shared_imports'; import { UIM_PIPELINES_LIST_LOAD } from '../../constants'; +import { getEditPath, getClonePath, getListPath } from '../../services/navigation'; import { EmptyList } from './empty_list'; import { PipelineTable } from './table'; @@ -67,17 +67,17 @@ export const PipelinesList: React.FunctionComponent = ({ } }, [pipelineNameFromLocation, data]); - const goToEditPipeline = (name: string) => { - history.push(`${BASE_PATH}/edit/${encodeURIComponent(name)}`); + const goToEditPipeline = (pipelineName: string) => { + history.push(getEditPath({ pipelineName })); }; - const goToClonePipeline = (name: string) => { - history.push(`${BASE_PATH}/create/${encodeURIComponent(name)}`); + const goToClonePipeline = (clonedPipelineName: string) => { + history.push(getClonePath({ clonedPipelineName })); }; const goHome = () => { setShowFlyout(false); - history.push(BASE_PATH); + history.push(getListPath()); }; if (data && data.length === 0) { diff --git a/x-pack/plugins/ingest_pipelines/public/application/services/navigation.ts b/x-pack/plugins/ingest_pipelines/public/application/services/navigation.ts new file mode 100644 index 0000000000000..3ac3de6eac710 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/services/navigation.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; + * you may not use this file except in compliance with the Elastic License. + */ + +const BASE_PATH = '/'; + +const EDIT_PATH = 'edit'; + +const CREATE_PATH = 'create'; + +const _getEditPath = (name: string, encode = true): string => { + return `${BASE_PATH}${EDIT_PATH}/${encode ? encodeURIComponent(name) : name}`; +}; + +const _getCreatePath = (): string => { + return `${BASE_PATH}${CREATE_PATH}`; +}; + +const _getClonePath = (name: string, encode = true): string => { + return `${BASE_PATH}${CREATE_PATH}/${encode ? encodeURIComponent(name) : name}`; +}; +const _getListPath = (name?: string): string => { + return `${BASE_PATH}${name ? `?pipeline=${encodeURIComponent(name)}` : ''}`; +}; + +export const ROUTES = { + list: _getListPath(), + edit: _getEditPath(':name', false), + create: _getCreatePath(), + clone: _getClonePath(':sourceName', false), +}; + +export const getListPath = ({ + inspectedPipelineName, +}: { + inspectedPipelineName?: string; +} = {}): string => _getListPath(inspectedPipelineName); +export const getEditPath = ({ pipelineName }: { pipelineName: string }): string => + _getEditPath(pipelineName, true); +export const getCreatePath = (): string => _getCreatePath(); +export const getClonePath = ({ clonedPipelineName }: { clonedPipelineName: string }): string => + _getClonePath(clonedPipelineName, true); diff --git a/x-pack/plugins/ingest_pipelines/public/index.ts b/x-pack/plugins/ingest_pipelines/public/index.ts index 7247973703804..637d4aad7264a 100644 --- a/x-pack/plugins/ingest_pipelines/public/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/index.ts @@ -9,3 +9,10 @@ import { IngestPipelinesPlugin } from './plugin'; export function plugin() { return new IngestPipelinesPlugin(); } + +export { + INGEST_PIPELINES_APP_ULR_GENERATOR, + IngestPipelinesUrlGenerator, + IngestPipelinesUrlGeneratorState, + INGEST_PIPELINES_PAGES, +} from './url_generator'; diff --git a/x-pack/plugins/ingest_pipelines/public/plugin.ts b/x-pack/plugins/ingest_pipelines/public/plugin.ts index 339068f185d1d..6c2f4a0898327 100644 --- a/x-pack/plugins/ingest_pipelines/public/plugin.ts +++ b/x-pack/plugins/ingest_pipelines/public/plugin.ts @@ -10,10 +10,11 @@ import { CoreSetup, Plugin } from 'src/core/public'; import { PLUGIN_ID } from '../common/constants'; import { uiMetricService, apiService } from './application/services'; import { Dependencies } from './types'; +import { registerUrlGenerator } from './url_generator'; export class IngestPipelinesPlugin implements Plugin { public setup(coreSetup: CoreSetup, plugins: Dependencies): void { - const { management, usageCollection } = plugins; + const { management, usageCollection, share } = plugins; const { http, getStartServices } = coreSetup; // Initialize services @@ -46,6 +47,8 @@ export class IngestPipelinesPlugin implements Plugin { }; }, }); + + registerUrlGenerator(coreSetup, management, share); } public start() {} diff --git a/x-pack/plugins/ingest_pipelines/public/types.ts b/x-pack/plugins/ingest_pipelines/public/types.ts index 91783ea04fa9a..e968c87226d07 100644 --- a/x-pack/plugins/ingest_pipelines/public/types.ts +++ b/x-pack/plugins/ingest_pipelines/public/types.ts @@ -6,8 +6,10 @@ import { ManagementSetup } from 'src/plugins/management/public'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; +import { SharePluginSetup } from '../../../../src/plugins/share/public'; export interface Dependencies { management: ManagementSetup; usageCollection: UsageCollectionSetup; + share: SharePluginSetup; } diff --git a/x-pack/plugins/ingest_pipelines/public/url_generator.test.ts b/x-pack/plugins/ingest_pipelines/public/url_generator.test.ts new file mode 100644 index 0000000000000..1267d526fb7d4 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/url_generator.test.ts @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IngestPipelinesUrlGenerator, INGEST_PIPELINES_PAGES } from './url_generator'; + +describe('IngestPipelinesUrlGenerator', () => { + const getAppBasePath = (absolute: boolean = false) => { + if (absolute) { + return Promise.resolve('http://localhost/app/test_app'); + } + return Promise.resolve('/app/test_app'); + }; + const urlGenerator = new IngestPipelinesUrlGenerator(getAppBasePath); + + describe('Pipelines List', () => { + it('generates relative url for list without pipelineId', async () => { + const url = await urlGenerator.createUrl({ + page: INGEST_PIPELINES_PAGES.LIST, + }); + expect(url).toBe('/app/test_app/'); + }); + + it('generates absolute url for list without pipelineId', async () => { + const url = await urlGenerator.createUrl({ + page: INGEST_PIPELINES_PAGES.LIST, + absolute: true, + }); + expect(url).toBe('http://localhost/app/test_app/'); + }); + it('generates relative url for list with a pipelineId', async () => { + const url = await urlGenerator.createUrl({ + page: INGEST_PIPELINES_PAGES.LIST, + pipelineId: 'pipeline_name', + }); + expect(url).toBe('/app/test_app/?pipeline=pipeline_name'); + }); + + it('generates absolute url for list with a pipelineId', async () => { + const url = await urlGenerator.createUrl({ + page: INGEST_PIPELINES_PAGES.LIST, + pipelineId: 'pipeline_name', + absolute: true, + }); + expect(url).toBe('http://localhost/app/test_app/?pipeline=pipeline_name'); + }); + }); + + describe('Pipeline Edit', () => { + it('generates relative url for pipeline edit', async () => { + const url = await urlGenerator.createUrl({ + page: INGEST_PIPELINES_PAGES.EDIT, + pipelineId: 'pipeline_name', + }); + expect(url).toBe('/app/test_app/edit/pipeline_name'); + }); + + it('generates absolute url for pipeline edit', async () => { + const url = await urlGenerator.createUrl({ + page: INGEST_PIPELINES_PAGES.EDIT, + pipelineId: 'pipeline_name', + absolute: true, + }); + expect(url).toBe('http://localhost/app/test_app/edit/pipeline_name'); + }); + }); + + describe('Pipeline Clone', () => { + it('generates relative url for pipeline clone', async () => { + const url = await urlGenerator.createUrl({ + page: INGEST_PIPELINES_PAGES.CLONE, + pipelineId: 'pipeline_name', + }); + expect(url).toBe('/app/test_app/create/pipeline_name'); + }); + + it('generates absolute url for pipeline clone', async () => { + const url = await urlGenerator.createUrl({ + page: INGEST_PIPELINES_PAGES.CLONE, + pipelineId: 'pipeline_name', + absolute: true, + }); + expect(url).toBe('http://localhost/app/test_app/create/pipeline_name'); + }); + }); + + describe('Pipeline Create', () => { + it('generates relative url for pipeline create', async () => { + const url = await urlGenerator.createUrl({ + page: INGEST_PIPELINES_PAGES.CREATE, + pipelineId: 'pipeline_name', + }); + expect(url).toBe('/app/test_app/create'); + }); + + it('generates absolute url for pipeline create', async () => { + const url = await urlGenerator.createUrl({ + page: INGEST_PIPELINES_PAGES.CREATE, + pipelineId: 'pipeline_name', + absolute: true, + }); + expect(url).toBe('http://localhost/app/test_app/create'); + }); + }); +}); diff --git a/x-pack/plugins/ingest_pipelines/public/url_generator.ts b/x-pack/plugins/ingest_pipelines/public/url_generator.ts new file mode 100644 index 0000000000000..043d449a0440a --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/url_generator.ts @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreSetup } from 'src/core/public'; +import { MANAGEMENT_APP_ID } from '../../../../src/plugins/management/public'; +import { UrlGeneratorsDefinition } from '../../../../src/plugins/share/public'; +import { + getClonePath, + getCreatePath, + getEditPath, + getListPath, +} from './application/services/navigation'; +import { Dependencies } from './types'; +import { PLUGIN_ID } from '../common/constants'; + +export const INGEST_PIPELINES_APP_ULR_GENERATOR = 'INGEST_PIPELINES_APP_URL_GENERATOR'; + +export enum INGEST_PIPELINES_PAGES { + LIST = 'pipelines_list', + EDIT = 'pipeline_edit', + CREATE = 'pipeline_create', + CLONE = 'pipeline_clone', +} + +interface UrlGeneratorState { + pipelineId: string; + absolute?: boolean; +} +export interface PipelinesListUrlGeneratorState extends Partial { + page: INGEST_PIPELINES_PAGES.LIST; +} + +export interface PipelineEditUrlGeneratorState extends UrlGeneratorState { + page: INGEST_PIPELINES_PAGES.EDIT; +} + +export interface PipelineCloneUrlGeneratorState extends UrlGeneratorState { + page: INGEST_PIPELINES_PAGES.CLONE; +} + +export interface PipelineCreateUrlGeneratorState extends UrlGeneratorState { + page: INGEST_PIPELINES_PAGES.CREATE; +} + +export type IngestPipelinesUrlGeneratorState = + | PipelinesListUrlGeneratorState + | PipelineEditUrlGeneratorState + | PipelineCloneUrlGeneratorState + | PipelineCreateUrlGeneratorState; + +export class IngestPipelinesUrlGenerator + implements UrlGeneratorsDefinition { + constructor(private readonly getAppBasePath: (absolute: boolean) => Promise) {} + + public readonly id = INGEST_PIPELINES_APP_ULR_GENERATOR; + + public readonly createUrl = async (state: IngestPipelinesUrlGeneratorState): Promise => { + switch (state.page) { + case INGEST_PIPELINES_PAGES.EDIT: { + return `${await this.getAppBasePath(!!state.absolute)}${getEditPath({ + pipelineName: state.pipelineId, + })}`; + } + case INGEST_PIPELINES_PAGES.CREATE: { + return `${await this.getAppBasePath(!!state.absolute)}${getCreatePath()}`; + } + case INGEST_PIPELINES_PAGES.LIST: { + return `${await this.getAppBasePath(!!state.absolute)}${getListPath({ + inspectedPipelineName: state.pipelineId, + })}`; + } + case INGEST_PIPELINES_PAGES.CLONE: { + return `${await this.getAppBasePath(!!state.absolute)}${getClonePath({ + clonedPipelineName: state.pipelineId, + })}`; + } + } + }; +} + +export const registerUrlGenerator = ( + coreSetup: CoreSetup, + management: Dependencies['management'], + share: Dependencies['share'] +) => { + const getAppBasePath = async (absolute = false) => { + const [coreStart] = await coreSetup.getStartServices(); + return coreStart.application.getUrlForApp(MANAGEMENT_APP_ID, { + path: management.sections.section.ingest.getApp(PLUGIN_ID)!.basePath, + absolute: !!absolute, + }); + }; + + share.urlGenerators.registerUrlGenerator(new IngestPipelinesUrlGenerator(getAppBasePath)); +}; diff --git a/x-pack/plugins/lens/common/constants.ts b/x-pack/plugins/lens/common/constants.ts index ea2331a577743..d30ab5962667d 100644 --- a/x-pack/plugins/lens/common/constants.ts +++ b/x-pack/plugins/lens/common/constants.ts @@ -8,15 +8,16 @@ export const PLUGIN_ID = 'lens'; export const LENS_EMBEDDABLE_TYPE = 'lens'; export const NOT_INTERNATIONALIZED_PRODUCT_NAME = 'Lens Visualizations'; export const BASE_API_URL = '/api/lens'; +export const LENS_EDIT_BY_VALUE = 'edit_by_value'; export function getBasePath() { return `#/`; } -export function getEditPath(id: string) { - return `#/edit/${encodeURIComponent(id)}`; +export function getEditPath(id: string | undefined) { + return id ? `#/edit/${encodeURIComponent(id)}` : `#/${LENS_EDIT_BY_VALUE}`; } -export function getFullPath(id: string) { - return `/app/${PLUGIN_ID}${getEditPath(id)}`; +export function getFullPath(id?: string) { + return `/app/${PLUGIN_ID}${id ? getEditPath(id) : getBasePath()}`; } diff --git a/x-pack/plugins/lens/kibana.json b/x-pack/plugins/lens/kibana.json index 67d9d5ef64483..f5fba766e60ee 100644 --- a/x-pack/plugins/lens/kibana.json +++ b/x-pack/plugins/lens/kibana.json @@ -13,7 +13,7 @@ "dashboard", "charts" ], - "optionalPlugins": ["embeddable", "usageCollection", "taskManager", "uiActions"], + "optionalPlugins": ["embeddable", "usageCollection", "taskManager", "uiActions", "globalSearch"], "configPath": ["xpack", "lens"], "extraPublicDirs": ["common/constants"], "requiredBundles": ["savedObjects", "kibanaUtils", "kibanaReact", "embeddable"] 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 63c2a6b9b2f29..24114e2b31518 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -9,29 +9,36 @@ import { Observable } from 'rxjs'; import { ReactWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { App } from './app'; +import { LensAppProps, LensAppServices } from './types'; import { EditorFrameInstance } from '../types'; -import { AppMountParameters } from 'kibana/public'; -import { Storage } from '../../../../../src/plugins/kibana_utils/public'; -import { Document, SavedObjectStore } from '../persistence'; +import { Document, DOC_TYPE } from '../persistence'; import { mount } from 'enzyme'; +import { I18nProvider } from '@kbn/i18n/react'; import { SavedObjectSaveModal, checkForDuplicateTitle, } from '../../../../../src/plugins/saved_objects/public'; -import { createMemoryHistory, History } from 'history'; +import { createMemoryHistory } from 'history'; import { + DataPublicPluginStart, esFilters, FilterManager, IFieldType, IIndexPattern, UI_SETTINGS, } from '../../../../../src/plugins/data/public'; -import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks'; -const dataStartMock = dataPluginMock.createStartContract(); - import { navigationPluginMock } from '../../../../../src/plugins/navigation/public/mocks'; import { TopNavMenuData } from '../../../../../src/plugins/navigation/public'; import { coreMock } from 'src/core/public/mocks'; +import { + LensByValueInput, + LensSavedObjectAttributes, + LensByReferenceInput, +} from '../editor_frame_service/embeddable/embeddable'; +import { SavedObjectReference } from '../../../../../src/core/types'; +import { mockAttributeService } from '../../../../../src/plugins/dashboard/public/mocks'; +import { LensAttributeService } from '../lens_attribute_service'; +import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; jest.mock('../editor_frame_service/editor_frame/expression_helpers'); jest.mock('src/core/public'); @@ -120,39 +127,68 @@ function createMockTimefilter() { } describe('Lens App', () => { - let frame: jest.Mocked; let core: ReturnType; - let instance: ReactWrapper; - - function makeDefaultArgs(): jest.Mocked<{ - editorFrame: EditorFrameInstance; - data: typeof dataStartMock; - navigation: typeof navigationStartMock; - core: typeof core; - storage: Storage; - docId?: string; - docStorage: SavedObjectStore; - redirectTo: (id?: string, returnToOrigin?: boolean, newlyCreated?: boolean) => void; - originatingApp: string | undefined; - onAppLeave: AppMountParameters['onAppLeave']; - setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; - history: History; - getAppNameFromId?: (appId: string) => string | undefined; - }> { - return ({ - navigation: navigationStartMock, + let defaultDoc: Document; + let defaultSavedObjectId: string; + + const navMenuItems = { + expectedSaveButton: { emphasize: true, testId: 'lnsApp_saveButton' }, + expectedSaveAsButton: { emphasize: false, testId: 'lnsApp_saveButton' }, + expectedSaveAndReturnButton: { emphasize: true, testId: 'lnsApp_saveAndReturnButton' }, + }; + + function makeAttributeService(): LensAttributeService { + const attributeServiceMock = mockAttributeService< + LensSavedObjectAttributes, + LensByValueInput, + LensByReferenceInput + >( + DOC_TYPE, + { + customSaveMethod: jest.fn(), + customUnwrapMethod: jest.fn(), + }, + core + ); + attributeServiceMock.unwrapAttributes = jest.fn().mockResolvedValue(defaultDoc); + attributeServiceMock.wrapAttributes = jest + .fn() + .mockResolvedValue({ savedObjectId: defaultSavedObjectId }); + return attributeServiceMock; + } + + function makeDefaultProps(): jest.Mocked { + return { editorFrame: createMockFrame(), - core: { - ...core, - application: { - ...core.application, - capabilities: { - ...core.application.capabilities, - visualize: { save: true, saveQuery: true, show: true }, - }, + history: createMemoryHistory(), + redirectTo: jest.fn(), + redirectToOrigin: jest.fn(), + onAppLeave: jest.fn(), + setHeaderActionMenu: jest.fn(), + }; + } + + function makeDefaultServices(): jest.Mocked { + return { + http: core.http, + chrome: core.chrome, + overlays: core.overlays, + uiSettings: core.uiSettings, + navigation: navigationStartMock, + notifications: core.notifications, + attributeService: makeAttributeService(), + savedObjectsClient: core.savedObjects.client, + dashboardFeatureFlag: { allowByValueEmbeddables: false }, + getOriginatingAppName: jest.fn(() => 'defaultOriginatingApp'), + application: { + ...core.application, + capabilities: { + ...core.application.capabilities, + visualize: { save: true, saveQuery: true, show: true }, }, + getUrlForApp: jest.fn((appId: string) => `/testbasepath/app/${appId}#/`), }, - data: { + data: ({ query: { filterManager: createMockFilterManager(), timefilter: { @@ -166,38 +202,52 @@ describe('Lens App', () => { return new Promise((resolve) => resolve({ id })); }), }, - }, + } as unknown) as DataPublicPluginStart, storage: { get: jest.fn(), + set: jest.fn(), + remove: jest.fn(), + clear: jest.fn(), }, - docStorage: { - load: jest.fn(), - save: jest.fn(), - }, - redirectTo: jest.fn((id?: string, returnToOrigin?: boolean, newlyCreated?: boolean) => {}), - onAppLeave: jest.fn(), - setHeaderActionMenu: jest.fn(), - history: createMemoryHistory(), - } as unknown) as jest.Mocked<{ - navigation: typeof navigationStartMock; - editorFrame: EditorFrameInstance; - data: typeof dataStartMock; - core: typeof core; - storage: Storage; - docId?: string; - docStorage: SavedObjectStore; - redirectTo: (id?: string, returnToOrigin?: boolean, newlyCreated?: boolean) => void; - originatingApp: string | undefined; - onAppLeave: AppMountParameters['onAppLeave']; - setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; - history: History; - getAppNameFromId?: (appId: string) => string | undefined; - }>; + }; + } + + function mountWith({ + props: incomingProps, + services: incomingServices, + }: { + props?: jest.Mocked; + services?: jest.Mocked; + }) { + const props = incomingProps ?? makeDefaultProps(); + const services = incomingServices ?? makeDefaultServices(); + const wrappingComponent: React.FC<{ + children: React.ReactNode; + }> = ({ children }) => { + return ( + + {children} + + ); + }; + const frame = props.editorFrame as ReturnType; + const component = mount(, { wrappingComponent }); + return { component, frame, props, services }; } beforeEach(() => { - frame = createMockFrame(); core = coreMock.createStart({ basePath: '/testbasepath' }); + defaultSavedObjectId = '1234'; + defaultDoc = ({ + savedObjectId: defaultSavedObjectId, + title: 'An extremely cool default document!', + expression: 'definitely a valid expression', + state: { + query: 'kuery', + filters: [{ query: { match_phrase: { src: 'test' } } }], + }, + references: [{ type: 'index-pattern', id: '1', name: 'index-pattern-0' }], + } as unknown) as Document; core.uiSettings.get.mockImplementation( jest.fn((type) => { @@ -215,10 +265,7 @@ describe('Lens App', () => { }); it('renders the editor frame', () => { - const args = makeDefaultArgs(); - args.editorFrame = frame; - - mount(); + const { frame } = mountWith({}); expect(frame.mount.mock.calls).toMatchInlineSnapshot(` Array [ @@ -248,23 +295,22 @@ describe('Lens App', () => { }); it('clears app filters on load', () => { - const defaultArgs = makeDefaultArgs(); - mount(); - - expect(defaultArgs.data.query.filterManager.setAppFilters).toHaveBeenCalledWith([]); + const { services } = mountWith({}); + expect(services.data.query.filterManager.setAppFilters).toHaveBeenCalledWith([]); }); it('passes global filters to frame', async () => { - const args = makeDefaultArgs(); - args.editorFrame = frame; + const services = makeDefaultServices(); const indexPattern = ({ id: 'index1' } as unknown) as IIndexPattern; const pinnedField = ({ name: 'pinnedField' } as unknown) as IFieldType; const pinnedFilter = esFilters.buildExistsFilter(pinnedField, indexPattern); - args.data.query.filterManager.getFilters = jest.fn().mockImplementation(() => { + services.data.query.filterManager.getFilters = jest.fn().mockImplementation(() => { return [pinnedFilter]; }); - const component = mount(); + const { component, frame } = mountWith({ services }); + component.update(); + expect(frame.mount).toHaveBeenCalledWith( expect.any(Element), expect.objectContaining({ @@ -275,103 +321,81 @@ describe('Lens App', () => { ); }); - it('sets breadcrumbs when the document title changes', async () => { - const defaultArgs = makeDefaultArgs(); - instance = mount(); - - expect(core.chrome.setBreadcrumbs).toHaveBeenCalledWith([ - { text: 'Visualize', href: '/testbasepath/app/visualize#/', onClick: expect.anything() }, - { text: 'Create' }, - ]); + it('displays errors from the frame in a toast', () => { + const { component, frame, services } = mountWith({}); + const onError = frame.mount.mock.calls[0][1].onError; + onError({ message: 'error' }); + component.update(); + expect(services.notifications.toasts.addDanger).toHaveBeenCalled(); + }); - (defaultArgs.docStorage.load as jest.Mock).mockResolvedValue({ - id: '1234', + describe('breadcrumbs', () => { + const breadcrumbDocSavedObjectId = defaultSavedObjectId; + const breadcrumbDoc = ({ + savedObjectId: breadcrumbDocSavedObjectId, title: 'Daaaaaaadaumching!', state: { query: 'fake query', filters: [], }, references: [], - }); - await act(async () => { - instance.setProps({ docId: '1234' }); - }); + } as unknown) as Document; - expect(defaultArgs.core.chrome.setBreadcrumbs).toHaveBeenCalledWith([ - { text: 'Visualize', href: '/testbasepath/app/visualize#/', onClick: expect.anything() }, - { text: 'Daaaaaaadaumching!' }, - ]); - }); + it('sets breadcrumbs when the document title changes', async () => { + const { component, services } = mountWith({}); - it('adds to the recently viewed list on load', async () => { - const defaultArgs = makeDefaultArgs(); - instance = mount(); + expect(services.chrome.setBreadcrumbs).toHaveBeenCalledWith([ + { text: 'Visualize', href: '/testbasepath/app/visualize#/', onClick: expect.anything() }, + { text: 'Create' }, + ]); - (defaultArgs.docStorage.load as jest.Mock).mockResolvedValue({ - id: '1234', - title: 'Daaaaaaadaumching!', - state: { - query: 'fake query', - filters: [], - }, - references: [], - }); - await act(async () => { - instance.setProps({ docId: '1234' }); + services.attributeService.unwrapAttributes = jest.fn().mockResolvedValue(breadcrumbDoc); + await act(async () => { + component.setProps({ initialInput: { savedObjectId: breadcrumbDocSavedObjectId } }); + }); + + expect(services.chrome.setBreadcrumbs).toHaveBeenCalledWith([ + { text: 'Visualize', href: '/testbasepath/app/visualize#/', onClick: expect.anything() }, + { text: 'Daaaaaaadaumching!' }, + ]); }); - expect(defaultArgs.core.chrome.recentlyAccessed.add).toHaveBeenCalledWith( - '/app/lens#/edit/1234', - 'Daaaaaaadaumching!', - '1234' - ); - }); - it('sets originatingApp breadcrumb when the document title changes', async () => { - const defaultArgs = makeDefaultArgs(); - defaultArgs.originatingApp = 'ultraCoolDashboard'; - defaultArgs.getAppNameFromId = () => 'The Coolest Container Ever Made'; - instance = mount(); + it('sets originatingApp breadcrumb when the document title changes', async () => { + const props = makeDefaultProps(); + const services = makeDefaultServices(); + props.incomingState = { originatingApp: 'coolContainer' }; + services.getOriginatingAppName = jest.fn(() => 'The Coolest Container Ever Made'); + const { component } = mountWith({ props, services }); + + expect(services.chrome.setBreadcrumbs).toHaveBeenCalledWith([ + { text: 'The Coolest Container Ever Made', onClick: expect.anything() }, + { text: 'Visualize', href: '/testbasepath/app/visualize#/', onClick: expect.anything() }, + { text: 'Create' }, + ]); - expect(core.chrome.setBreadcrumbs).toHaveBeenCalledWith([ - { text: 'The Coolest Container Ever Made', onClick: expect.anything() }, - { text: 'Visualize', href: '/testbasepath/app/visualize#/', onClick: expect.anything() }, - { text: 'Create' }, - ]); + services.attributeService.unwrapAttributes = jest.fn().mockResolvedValue(breadcrumbDoc); + await act(async () => { + component.setProps({ initialInput: { savedObjectId: breadcrumbDocSavedObjectId } }); + }); - (defaultArgs.docStorage.load as jest.Mock).mockResolvedValue({ - id: '1234', - title: 'Daaaaaaadaumching!', - state: { - query: 'fake query', - filters: [], - }, - references: [], - }); - await act(async () => { - instance.setProps({ docId: '1234' }); + expect(services.chrome.setBreadcrumbs).toHaveBeenCalledWith([ + { text: 'The Coolest Container Ever Made', onClick: expect.anything() }, + { text: 'Visualize', href: '/testbasepath/app/visualize#/', onClick: expect.anything() }, + { text: 'Daaaaaaadaumching!' }, + ]); }); - - expect(defaultArgs.core.chrome.setBreadcrumbs).toHaveBeenCalledWith([ - { text: 'The Coolest Container Ever Made', onClick: expect.anything() }, - { text: 'Visualize', href: '/testbasepath/app/visualize#/', onClick: expect.anything() }, - { text: 'Daaaaaaadaumching!' }, - ]); }); describe('persistence', () => { - it('does not load a document if there is no document id', () => { - const args = makeDefaultArgs(); - - mount(); - - expect(args.docStorage.load).not.toHaveBeenCalled(); + it('does not load a document if there is no initial input', () => { + const { services } = mountWith({}); + expect(services.attributeService.unwrapAttributes).not.toHaveBeenCalled(); }); - it('loads a document and uses query and filters if there is a document id', async () => { - const args = makeDefaultArgs(); - args.editorFrame = frame; - (args.docStorage.load as jest.Mock).mockResolvedValue({ - id: '1234', + it('loads a document and uses query and filters if initial input is provided', async () => { + const { component, frame, services } = mountWith({}); + services.attributeService.unwrapAttributes = jest.fn().mockResolvedValue({ + savedObjectId: defaultSavedObjectId, state: { query: 'fake query', filters: [{ query: { match_phrase: { src: 'test' } } }], @@ -379,15 +403,15 @@ describe('Lens App', () => { references: [{ type: 'index-pattern', id: '1', name: 'index-pattern-0' }], }); - instance = mount(); - await act(async () => { - instance.setProps({ docId: '1234' }); + component.setProps({ initialInput: { savedObjectId: defaultSavedObjectId } }); }); - expect(args.docStorage.load).toHaveBeenCalledWith('1234'); - expect(args.data.indexPatterns.get).toHaveBeenCalledWith('1'); - expect(args.data.query.filterManager.setAppFilters).toHaveBeenCalledWith([ + expect(services.attributeService.unwrapAttributes).toHaveBeenCalledWith({ + savedObjectId: defaultSavedObjectId, + }); + expect(services.data.indexPatterns.get).toHaveBeenCalledWith('1'); + expect(services.data.query.filterManager.setAppFilters).toHaveBeenCalledWith([ { query: { match_phrase: { src: 'test' } } }, ]); expect(TopNavMenu).toHaveBeenCalledWith( @@ -401,7 +425,7 @@ describe('Lens App', () => { expect.any(Element), expect.objectContaining({ doc: expect.objectContaining({ - id: '1234', + savedObjectId: defaultSavedObjectId, state: expect.objectContaining({ query: 'fake query', filters: [{ query: { match_phrase: { src: 'test' } } }], @@ -412,65 +436,59 @@ describe('Lens App', () => { }); it('does not load documents on sequential renders unless the id changes', async () => { - const args = makeDefaultArgs(); - args.editorFrame = frame; - (args.docStorage.load as jest.Mock).mockResolvedValue({ id: '1234' }); + const { services, component } = mountWith({}); - instance = mount(); await act(async () => { - instance.setProps({ docId: '1234' }); + component.setProps({ initialInput: { savedObjectId: defaultSavedObjectId } }); }); - await act(async () => { - instance.setProps({ docId: '1234' }); + component.setProps({ initialInput: { savedObjectId: defaultSavedObjectId } }); }); - - expect(args.docStorage.load).toHaveBeenCalledTimes(1); + expect(services.attributeService.unwrapAttributes).toHaveBeenCalledTimes(1); await act(async () => { - instance.setProps({ docId: '9876' }); + component.setProps({ initialInput: { savedObjectId: '5678' } }); }); - expect(args.docStorage.load).toHaveBeenCalledTimes(2); + expect(services.attributeService.unwrapAttributes).toHaveBeenCalledTimes(2); }); it('handles document load errors', async () => { - const args = makeDefaultArgs(); - args.editorFrame = frame; - (args.docStorage.load as jest.Mock).mockRejectedValue('failed to load'); - - instance = mount(); + const services = makeDefaultServices(); + services.attributeService.unwrapAttributes = jest.fn().mockRejectedValue('failed to load'); + const { component, props } = mountWith({ services }); await act(async () => { - instance.setProps({ docId: '1234' }); + component.setProps({ initialInput: { savedObjectId: defaultSavedObjectId } }); }); - expect(args.docStorage.load).toHaveBeenCalledWith('1234'); - expect(args.core.notifications.toasts.addDanger).toHaveBeenCalled(); - expect(args.redirectTo).toHaveBeenCalled(); + expect(services.attributeService.unwrapAttributes).toHaveBeenCalledWith({ + savedObjectId: defaultSavedObjectId, + }); + expect(services.notifications.toasts.addDanger).toHaveBeenCalled(); + expect(props.redirectTo).toHaveBeenCalled(); }); - describe('save button', () => { + it('adds to the recently accessed list on load', async () => { + const { component, services } = mountWith({}); + + await act(async () => { + component.setProps({ initialInput: { savedObjectId: defaultSavedObjectId } }); + }); + expect(services.chrome.recentlyAccessed.add).toHaveBeenCalledWith( + '/app/lens#/edit/1234', + 'An extremely cool default document!', + '1234' + ); + }); + + describe('save buttons', () => { interface SaveProps { newCopyOnSave: boolean; returnToOrigin?: boolean; newTitle: string; } - let defaultArgs: ReturnType; - - beforeEach(() => { - defaultArgs = makeDefaultArgs(); - (defaultArgs.docStorage.load as jest.Mock).mockResolvedValue({ - id: '1234', - title: 'My cool doc', - expression: 'valid expression', - state: { - query: 'kuery', - }, - } as jest.ResolvedValue); - }); - function getButton(inst: ReactWrapper): TopNavMenuData { return (inst .find('[data-test-subj="lnsApp_topNav"]') @@ -495,135 +513,195 @@ describe('Lens App', () => { filters: [], }, }, - initialDocId, + initialSavedObjectId, ...saveProps }: SaveProps & { lastKnownDoc?: object; - initialDocId?: string; + initialSavedObjectId?: string; }) { - const args = { - ...defaultArgs, - docId: initialDocId, + const props = { + ...makeDefaultProps(), + initialInput: initialSavedObjectId + ? { savedObjectId: initialSavedObjectId, id: '5678' } + : undefined, }; - args.editorFrame = frame; - (args.docStorage.load as jest.Mock).mockResolvedValue({ - id: '1234', + + const services = makeDefaultServices(); + services.attributeService.wrapAttributes = jest + .fn() + .mockImplementation(async ({ savedObjectId }) => ({ + savedObjectId: savedObjectId || 'aaa', + })); + services.attributeService.unwrapAttributes = jest.fn().mockResolvedValue({ + savedObjectId: initialSavedObjectId ?? 'aaa', references: [], state: { query: 'fake query', filters: [], }, - }); - (args.docStorage.save as jest.Mock).mockImplementation(async ({ id }) => ({ - id: id || 'aaa', - })); + } as jest.ResolvedValue); + let frame: jest.Mocked = {} as jest.Mocked; + let component: ReactWrapper = {} as ReactWrapper; await act(async () => { - instance = mount(); + const { frame: newFrame, component: newComponent } = mountWith({ services, props }); + frame = newFrame; + component = newComponent; }); - if (initialDocId) { - expect(args.docStorage.load).toHaveBeenCalledTimes(1); + if (initialSavedObjectId) { + expect(services.attributeService.unwrapAttributes).toHaveBeenCalledTimes(1); } else { - expect(args.docStorage.load).not.toHaveBeenCalled(); + expect(services.attributeService.unwrapAttributes).not.toHaveBeenCalled(); } const onChange = frame.mount.mock.calls[0][1].onChange; + act(() => onChange({ filterableIndexPatterns: [], - doc: { id: initialDocId, ...lastKnownDoc } as Document, + doc: { savedObjectId: initialSavedObjectId, ...lastKnownDoc } as Document, isSaveable: true, }) ); - - instance.update(); - - expect(getButton(instance).disableButton).toEqual(false); - + component.update(); + expect(getButton(component).disableButton).toEqual(false); await act(async () => { - testSave(instance, { ...saveProps }); + testSave(component, { ...saveProps }); }); - - return { args, instance }; + return { props, services, component, frame }; } it('shows a disabled save button when the user does not have permissions', async () => { - const args = defaultArgs; - args.core.application = { - ...args.core.application, + const services = makeDefaultServices(); + services.application = { + ...services.application, capabilities: { - ...args.core.application.capabilities, + ...services.application.capabilities, visualize: { save: false, saveQuery: false, show: true }, }, }; - args.editorFrame = frame; - - instance = mount(); - - expect(getButton(instance).disableButton).toEqual(true); - + const { component, frame } = mountWith({ services }); + expect(getButton(component).disableButton).toEqual(true); const onChange = frame.mount.mock.calls[0][1].onChange; act(() => onChange({ filterableIndexPatterns: [], - doc: ({ id: 'will save this' } as unknown) as Document, + doc: ({ savedObjectId: 'will save this' } as unknown) as Document, isSaveable: true, }) ); - instance.update(); - expect(getButton(instance).disableButton).toEqual(true); + component.update(); + expect(getButton(component).disableButton).toEqual(true); }); - it('shows a save button that is enabled when the frame has provided its state', async () => { - const args = defaultArgs; - args.editorFrame = frame; - - instance = mount(); - - expect(getButton(instance).disableButton).toEqual(true); - + it('shows a save button that is enabled when the frame has provided its state and does not show save and return or save as', async () => { + const { component, frame } = mountWith({}); + expect(getButton(component).disableButton).toEqual(true); const onChange = frame.mount.mock.calls[0][1].onChange; act(() => onChange({ filterableIndexPatterns: [], - doc: ({ id: 'will save this' } as unknown) as Document, + doc: ({ savedObjectId: 'will save this' } as unknown) as Document, isSaveable: true, }) ); - instance.update(); + component.update(); + expect(getButton(component).disableButton).toEqual(false); - expect(getButton(instance).disableButton).toEqual(false); + await act(async () => { + const topNavMenuConfig = component.find(TopNavMenu).prop('config'); + expect(topNavMenuConfig).not.toContainEqual( + expect.objectContaining(navMenuItems.expectedSaveAndReturnButton) + ); + expect(topNavMenuConfig).not.toContainEqual( + expect.objectContaining(navMenuItems.expectedSaveAsButton) + ); + expect(topNavMenuConfig).toContainEqual( + expect.objectContaining(navMenuItems.expectedSaveButton) + ); + }); + }); + + it('Shows Save and Return and Save As buttons in create by value mode', async () => { + const props = makeDefaultProps(); + const services = makeDefaultServices(); + services.dashboardFeatureFlag = { allowByValueEmbeddables: true }; + props.incomingState = { + originatingApp: 'ultraDashboard', + valueInput: { + id: 'whatchaGonnaDoWith', + attributes: { + title: + 'whatcha gonna do with all these references? All these references in your value Input', + references: [] as SavedObjectReference[], + }, + } as LensByValueInput, + }; + + const { component } = mountWith({ props, services }); + + await act(async () => { + const topNavMenuConfig = component.find(TopNavMenu).prop('config'); + expect(topNavMenuConfig).toContainEqual( + expect.objectContaining(navMenuItems.expectedSaveAndReturnButton) + ); + expect(topNavMenuConfig).toContainEqual( + expect.objectContaining(navMenuItems.expectedSaveAsButton) + ); + expect(topNavMenuConfig).not.toContainEqual( + expect.objectContaining(navMenuItems.expectedSaveButton) + ); + }); + }); + + it('Shows Save and Return and Save As buttons in edit by reference mode', async () => { + const props = makeDefaultProps(); + props.initialInput = { savedObjectId: defaultSavedObjectId, id: '5678' }; + props.incomingState = { + originatingApp: 'ultraDashboard', + }; + + const { component } = mountWith({ props }); + + await act(async () => { + const topNavMenuConfig = component.find(TopNavMenu).prop('config'); + expect(topNavMenuConfig).toContainEqual( + expect.objectContaining(navMenuItems.expectedSaveAndReturnButton) + ); + expect(topNavMenuConfig).toContainEqual( + expect.objectContaining(navMenuItems.expectedSaveAsButton) + ); + expect(topNavMenuConfig).not.toContainEqual( + expect.objectContaining(navMenuItems.expectedSaveButton) + ); + }); }); it('saves new docs', async () => { - const { args, instance: inst } = await save({ - initialDocId: undefined, + const { props, services } = await save({ + initialSavedObjectId: undefined, newCopyOnSave: false, newTitle: 'hello there', }); - - expect(args.docStorage.save).toHaveBeenCalledWith( + expect(services.attributeService.wrapAttributes).toHaveBeenCalledWith( expect.objectContaining({ - id: undefined, + savedObjectId: undefined, title: 'hello there', - }) + }), + true, + undefined ); - - expect(args.redirectTo).toHaveBeenCalledWith('aaa', undefined, true); - - inst.setProps({ docId: 'aaa' }); - - expect(args.docStorage.load).not.toHaveBeenCalled(); + expect(props.redirectTo).toHaveBeenCalledWith('aaa'); }); - it('adds to the recently viewed list on save', async () => { - const { args } = await save({ - initialDocId: undefined, + it('adds to the recently accessed list on save', async () => { + const { services } = await save({ + initialSavedObjectId: undefined, newCopyOnSave: false, newTitle: 'hello there', }); - expect(args.core.chrome.recentlyAccessed.add).toHaveBeenCalledWith( + expect(services.chrome.recentlyAccessed.add).toHaveBeenCalledWith( '/app/lens#/edit/aaa', 'hello there', 'aaa' @@ -631,54 +709,53 @@ describe('Lens App', () => { }); it('saves the latest doc as a copy', async () => { - const { args, instance: inst } = await save({ - initialDocId: '1234', + const { props, services, component } = await save({ + initialSavedObjectId: defaultSavedObjectId, newCopyOnSave: true, newTitle: 'hello there', }); - - expect(args.docStorage.save).toHaveBeenCalledWith( + expect(services.attributeService.wrapAttributes).toHaveBeenCalledWith( expect.objectContaining({ - id: undefined, + savedObjectId: undefined, title: 'hello there', - }) + }), + true, + undefined ); - - expect(args.redirectTo).toHaveBeenCalledWith('aaa', undefined, true); - - inst.setProps({ docId: 'aaa' }); - - expect(args.docStorage.load).toHaveBeenCalledTimes(1); + expect(props.redirectTo).toHaveBeenCalledWith('aaa'); + await act(async () => { + component.setProps({ initialInput: { savedObjectId: 'aaa' } }); + }); + expect(services.attributeService.wrapAttributes).toHaveBeenCalledTimes(1); }); it('saves existing docs', async () => { - const { args, instance: inst } = await save({ - initialDocId: '1234', + const { props, services, component } = await save({ + initialSavedObjectId: defaultSavedObjectId, newCopyOnSave: false, newTitle: 'hello there', }); - - expect(args.docStorage.save).toHaveBeenCalledWith( + expect(services.attributeService.wrapAttributes).toHaveBeenCalledWith( expect.objectContaining({ - id: '1234', + savedObjectId: defaultSavedObjectId, title: 'hello there', - }) + }), + true, + { id: '5678', savedObjectId: defaultSavedObjectId } ); - - expect(args.redirectTo).not.toHaveBeenCalled(); - - inst.setProps({ docId: '1234' }); - - expect(args.docStorage.load).toHaveBeenCalledTimes(1); + expect(props.redirectTo).not.toHaveBeenCalled(); + await act(async () => { + component.setProps({ initialInput: { savedObjectId: defaultSavedObjectId } }); + }); + expect(services.attributeService.unwrapAttributes).toHaveBeenCalledTimes(1); }); it('handles save failure by showing a warning, but still allows another save', async () => { - const args = defaultArgs; - args.editorFrame = frame; - (args.docStorage.save as jest.Mock).mockRejectedValue({ message: 'failed' }); - - instance = mount(); - + const services = makeDefaultServices(); + services.attributeService.wrapAttributes = jest + .fn() + .mockRejectedValue({ message: 'failed' }); + const { component, props, frame } = mountWith({ services }); const onChange = frame.mount.mock.calls[0][1].onChange; act(() => onChange({ @@ -687,51 +764,48 @@ describe('Lens App', () => { isSaveable: true, }) ); - - instance.update(); + component.update(); await act(async () => { - testSave(instance, { newCopyOnSave: false, newTitle: 'hello there' }); + testSave(component, { newCopyOnSave: false, newTitle: 'hello there' }); }); - - expect(args.core.notifications.toasts.addDanger).toHaveBeenCalled(); - expect(args.redirectTo).not.toHaveBeenCalled(); - - expect(getButton(instance).disableButton).toEqual(false); + expect(services.notifications.toasts.addDanger).toHaveBeenCalled(); + expect(props.redirectTo).not.toHaveBeenCalled(); + expect(getButton(component).disableButton).toEqual(false); }); it('saves new doc and redirects to originating app', async () => { - const { args } = await save({ - initialDocId: undefined, + const { props, services } = await save({ + initialSavedObjectId: undefined, returnToOrigin: true, newCopyOnSave: false, newTitle: 'hello there', }); - - expect(args.docStorage.save).toHaveBeenCalledWith( + expect(services.attributeService.wrapAttributes).toHaveBeenCalledWith( expect.objectContaining({ - id: undefined, + savedObjectId: undefined, title: 'hello there', - }) + }), + true, + undefined ); - - expect(args.redirectTo).toHaveBeenCalledWith('aaa', true, true); + expect(props.redirectToOrigin).toHaveBeenCalledWith({ + input: { savedObjectId: 'aaa' }, + isCopied: false, + }); }); it('saves app filters and does not save pinned filters', async () => { const indexPattern = ({ id: 'index1' } as unknown) as IIndexPattern; const field = ({ name: 'myfield' } as unknown) as IFieldType; const pinnedField = ({ name: 'pinnedField' } as unknown) as IFieldType; - const unpinned = esFilters.buildExistsFilter(field, indexPattern); const pinned = esFilters.buildExistsFilter(pinnedField, indexPattern); - await act(async () => { FilterManager.setFiltersStore([pinned], esFilters.FilterStateStore.GLOBAL_STATE); }); - - const { args } = await save({ - initialDocId: '1234', + const { services } = await save({ + initialSavedObjectId: defaultSavedObjectId, newCopyOnSave: false, newTitle: 'hello there2', lastKnownDoc: { @@ -741,42 +815,42 @@ describe('Lens App', () => { }, }, }); - - expect(args.docStorage.save).toHaveBeenCalledWith({ - id: '1234', - title: 'hello there2', - expression: 'kibana 3', - state: { - filters: [unpinned], + expect(services.attributeService.wrapAttributes).toHaveBeenCalledWith( + { + savedObjectId: defaultSavedObjectId, + title: 'hello there2', + expression: 'kibana 3', + state: { + filters: [unpinned], + }, }, - }); + true, + { id: '5678', savedObjectId: defaultSavedObjectId } + ); }); it('checks for duplicate title before saving', async () => { - const args = defaultArgs; - args.editorFrame = frame; - (args.docStorage.save as jest.Mock).mockReturnValue(Promise.resolve({ id: '123' })); - - instance = mount(); - + const services = makeDefaultServices(); + services.attributeService.wrapAttributes = jest + .fn() + .mockReturnValue(Promise.resolve({ savedObjectId: '123' })); + const { component, frame } = mountWith({ services }); const onChange = frame.mount.mock.calls[0][1].onChange; await act(async () => onChange({ filterableIndexPatterns: [], - doc: ({ id: '123' } as unknown) as Document, + doc: ({ savedObjectId: '123' } as unknown) as Document, isSaveable: true, }) ); - instance.update(); + component.update(); await act(async () => { - getButton(instance).run(instance.getDOMNode()); + getButton(component).run(component.getDOMNode()); }); - instance.update(); - + component.update(); const onTitleDuplicate = jest.fn(); - await act(async () => { - instance.find(SavedObjectSaveModal).prop('onSave')({ + component.find(SavedObjectSaveModal).prop('onSave')({ onTitleDuplicate, isTitleDuplicateConfirmed: false, newCopyOnSave: false, @@ -784,9 +858,8 @@ describe('Lens App', () => { newTitle: 'test', }); }); - expect(checkForDuplicateTitle).toHaveBeenCalledWith( - expect.objectContaining({ id: '123' }), + expect.objectContaining({ savedObjectId: '123' }), false, onTitleDuplicate, expect.anything() @@ -794,11 +867,7 @@ describe('Lens App', () => { }); it('does not show the copy button on first save', async () => { - const args = defaultArgs; - args.editorFrame = frame; - - instance = mount(); - + const { component, frame } = mountWith({}); const onChange = frame.mount.mock.calls[0][1].onChange; await act(async () => onChange({ @@ -807,36 +876,17 @@ describe('Lens App', () => { isSaveable: true, }) ); - instance.update(); - await act(async () => getButton(instance).run(instance.getDOMNode())); - instance.update(); - - expect(instance.find(SavedObjectSaveModal).prop('showCopyOnSave')).toEqual(false); + component.update(); + await act(async () => getButton(component).run(component.getDOMNode())); + component.update(); + expect(component.find(SavedObjectSaveModal).prop('showCopyOnSave')).toEqual(false); }); }); }); describe('query bar state management', () => { - let defaultArgs: ReturnType; - - beforeEach(() => { - defaultArgs = makeDefaultArgs(); - (defaultArgs.docStorage.load as jest.Mock).mockResolvedValue({ - id: '1234', - title: 'My cool doc', - expression: 'valid expression', - state: { - query: 'kuery', - }, - } as jest.ResolvedValue); - }); - it('uses the default time and query language settings', () => { - const args = defaultArgs; - args.editorFrame = frame; - - mount(); - + const { frame } = mountWith({}); expect(TopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ query: { query: '', language: 'kuery' }, @@ -855,20 +905,14 @@ describe('Lens App', () => { }); it('updates the index patterns when the editor frame is changed', async () => { - const args = defaultArgs; - args.editorFrame = frame; - - instance = mount(); - + const { component, frame } = mountWith({}); expect(TopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ indexPatterns: [], }), {} ); - const onChange = frame.mount.mock.calls[0][1].onChange; - await act(async () => { onChange({ filterableIndexPatterns: ['1'], @@ -876,18 +920,14 @@ describe('Lens App', () => { isSaveable: true, }); }); - - instance.update(); - + component.update(); expect(TopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ indexPatterns: [{ id: '1' }], }), {} ); - // Do it again to verify that the dirty checking is done right - await act(async () => { onChange({ filterableIndexPatterns: ['2'], @@ -895,9 +935,7 @@ describe('Lens App', () => { isSaveable: true, }); }); - - instance.update(); - + component.update(); expect(TopNavMenu).toHaveBeenLastCalledWith( expect.objectContaining({ indexPatterns: [{ id: '2' }], @@ -905,21 +943,16 @@ describe('Lens App', () => { {} ); }); - it('updates the editor frame when the user changes query or time in the search bar', () => { - const args = defaultArgs; - args.editorFrame = frame; - - instance = mount(); + it('updates the editor frame when the user changes query or time in the search bar', () => { + const { component, frame } = mountWith({}); act(() => - instance.find(TopNavMenu).prop('onQuerySubmit')!({ + component.find(TopNavMenu).prop('onQuerySubmit')!({ dateRange: { from: 'now-14d', to: 'now-7d' }, query: { query: 'new', language: 'lucene' }, }) ); - - instance.update(); - + component.update(); expect(TopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ query: { query: 'new', language: 'lucene' }, @@ -938,19 +971,15 @@ describe('Lens App', () => { }); it('updates the filters when the user changes them', () => { - const args = defaultArgs; - args.editorFrame = frame; - - instance = mount(); + const { component, frame, services } = mountWith({}); const indexPattern = ({ id: 'index1' } as unknown) as IIndexPattern; const field = ({ name: 'myfield' } as unknown) as IFieldType; - act(() => - args.data.query.filterManager.setFilters([esFilters.buildExistsFilter(field, indexPattern)]) + services.data.query.filterManager.setFilters([ + esFilters.buildExistsFilter(field, indexPattern), + ]) ); - - instance.update(); - + component.update(); expect(frame.mount).toHaveBeenCalledWith( expect.any(Element), expect.objectContaining({ @@ -962,17 +991,15 @@ describe('Lens App', () => { describe('saved query handling', () => { it('does not allow saving when the user is missing the saveQuery permission', () => { - const args = makeDefaultArgs(); - args.core.application = { - ...args.core.application, + const services = makeDefaultServices(); + services.application = { + ...services.application, capabilities: { - ...args.core.application.capabilities, + ...services.application.capabilities, visualize: { save: false, saveQuery: false, show: true }, }, }; - - mount(); - + mountWith({ services }); expect(TopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ showSaveQuery: false }), {} @@ -980,11 +1007,7 @@ describe('Lens App', () => { }); it('persists the saved query ID when the query is saved', () => { - const args = makeDefaultArgs(); - args.editorFrame = frame; - - instance = mount(); - + const { component } = mountWith({}); expect(TopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ showSaveQuery: true, @@ -995,9 +1018,8 @@ describe('Lens App', () => { }), {} ); - act(() => { - instance.find(TopNavMenu).prop('onSaved')!({ + component.find(TopNavMenu).prop('onSaved')!({ id: '1', attributes: { title: '', @@ -1006,7 +1028,6 @@ describe('Lens App', () => { }, }); }); - expect(TopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ savedQuery: { @@ -1023,13 +1044,9 @@ describe('Lens App', () => { }); it('changes the saved query ID when the query is updated', () => { - const args = makeDefaultArgs(); - args.editorFrame = frame; - - instance = mount(); - + const { component } = mountWith({}); act(() => { - instance.find(TopNavMenu).prop('onSaved')!({ + component.find(TopNavMenu).prop('onSaved')!({ id: '1', attributes: { title: '', @@ -1038,9 +1055,8 @@ describe('Lens App', () => { }, }); }); - act(() => { - instance.find(TopNavMenu).prop('onSavedQueryUpdated')!({ + component.find(TopNavMenu).prop('onSavedQueryUpdated')!({ id: '2', attributes: { title: 'new title', @@ -1049,7 +1065,6 @@ describe('Lens App', () => { }, }); }); - expect(TopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ savedQuery: { @@ -1066,32 +1081,23 @@ describe('Lens App', () => { }); it('clears all existing unpinned filters when the active saved query is cleared', () => { - const args = makeDefaultArgs(); - args.editorFrame = frame; - - instance = mount(); - + const { component, frame, services } = mountWith({}); act(() => - instance.find(TopNavMenu).prop('onQuerySubmit')!({ + component.find(TopNavMenu).prop('onQuerySubmit')!({ dateRange: { from: 'now-14d', to: 'now-7d' }, query: { query: 'new', language: 'lucene' }, }) ); - const indexPattern = ({ id: 'index1' } as unknown) as IIndexPattern; const field = ({ name: 'myfield' } as unknown) as IFieldType; const pinnedField = ({ name: 'pinnedField' } as unknown) as IFieldType; - const unpinned = esFilters.buildExistsFilter(field, indexPattern); const pinned = esFilters.buildExistsFilter(pinnedField, indexPattern); FilterManager.setFiltersStore([pinned], esFilters.FilterStateStore.GLOBAL_STATE); - - act(() => args.data.query.filterManager.setFilters([pinned, unpinned])); - instance.update(); - - act(() => instance.find(TopNavMenu).prop('onClearSavedQuery')!()); - instance.update(); - + act(() => services.data.query.filterManager.setFilters([pinned, unpinned])); + component.update(); + act(() => component.find(TopNavMenu).prop('onClearSavedQuery')!()); + component.update(); expect(frame.mount).toHaveBeenLastCalledWith( expect.any(Element), expect.objectContaining({ @@ -1101,191 +1107,127 @@ describe('Lens App', () => { }); }); - it('displays errors from the frame in a toast', () => { - const args = makeDefaultArgs(); - args.editorFrame = frame; - - instance = mount(); - - const onError = frame.mount.mock.calls[0][1].onError; - onError({ message: 'error' }); - - instance.update(); - - expect(args.core.notifications.toasts.addDanger).toHaveBeenCalled(); - }); - describe('showing a confirm message when leaving', () => { - let defaultArgs: ReturnType; let defaultLeave: jest.Mock; let confirmLeave: jest.Mock; beforeEach(() => { - defaultArgs = makeDefaultArgs(); defaultLeave = jest.fn(); confirmLeave = jest.fn(); - (defaultArgs.docStorage.load as jest.Mock).mockResolvedValue({ - id: '1234', - title: 'My cool doc', - state: { - query: 'kuery', - filters: [], - }, - references: [], - } as jest.ResolvedValue); }); it('should not show a confirm message if there is no expression to save', () => { - instance = mount(); - - const lastCall = - defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0]; + const { props } = mountWith({}); + const lastCall = props.onAppLeave.mock.calls[props.onAppLeave.mock.calls.length - 1][0]; lastCall({ default: defaultLeave, confirm: confirmLeave }); - expect(defaultLeave).toHaveBeenCalled(); expect(confirmLeave).not.toHaveBeenCalled(); }); it('does not confirm if the user is missing save permissions', () => { - const args = defaultArgs; - args.core.application = { - ...args.core.application, + const services = makeDefaultServices(); + services.application = { + ...services.application, capabilities: { - ...args.core.application.capabilities, + ...services.application.capabilities, visualize: { save: false, saveQuery: false, show: true }, }, }; - args.editorFrame = frame; - - instance = mount(); - + const { component, frame, props } = mountWith({ services }); const onChange = frame.mount.mock.calls[0][1].onChange; act(() => onChange({ filterableIndexPatterns: [], doc: ({ - id: undefined, - + savedObjectId: undefined, references: [], } as unknown) as Document, isSaveable: true, }) ); - instance.update(); - - const lastCall = - defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0]; + component.update(); + const lastCall = props.onAppLeave.mock.calls[props.onAppLeave.mock.calls.length - 1][0]; lastCall({ default: defaultLeave, confirm: confirmLeave }); - expect(defaultLeave).toHaveBeenCalled(); expect(confirmLeave).not.toHaveBeenCalled(); }); it('should confirm when leaving with an unsaved doc', () => { - defaultArgs.editorFrame = frame; - instance = mount(); - + const { component, frame, props } = mountWith({}); const onChange = frame.mount.mock.calls[0][1].onChange; act(() => onChange({ filterableIndexPatterns: [], - doc: ({ id: undefined, state: {} } as unknown) as Document, + doc: ({ savedObjectId: undefined, state: {} } as unknown) as Document, isSaveable: true, }) ); - instance.update(); - - const lastCall = - defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0]; + component.update(); + const lastCall = props.onAppLeave.mock.calls[props.onAppLeave.mock.calls.length - 1][0]; lastCall({ default: defaultLeave, confirm: confirmLeave }); - expect(confirmLeave).toHaveBeenCalled(); expect(defaultLeave).not.toHaveBeenCalled(); }); it('should confirm when leaving with unsaved changes to an existing doc', async () => { - defaultArgs.editorFrame = frame; - instance = mount(); + const { component, frame, props } = mountWith({}); await act(async () => { - instance.setProps({ docId: '1234' }); + component.setProps({ initialInput: { savedObjectId: defaultSavedObjectId } }); }); - const onChange = frame.mount.mock.calls[0][1].onChange; act(() => onChange({ filterableIndexPatterns: [], doc: ({ - id: '1234', - + savedObjectId: defaultSavedObjectId, references: [], } as unknown) as Document, isSaveable: true, }) ); - instance.update(); - - const lastCall = - defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0]; + component.update(); + const lastCall = props.onAppLeave.mock.calls[props.onAppLeave.mock.calls.length - 1][0]; lastCall({ default: defaultLeave, confirm: confirmLeave }); - expect(confirmLeave).toHaveBeenCalled(); expect(defaultLeave).not.toHaveBeenCalled(); }); it('should not confirm when changes are saved', async () => { - defaultArgs.editorFrame = frame; - instance = mount(); + const { component, frame, props } = mountWith({}); await act(async () => { - instance.setProps({ docId: '1234' }); + component.setProps({ initialInput: { savedObjectId: defaultSavedObjectId } }); }); - const onChange = frame.mount.mock.calls[0][1].onChange; act(() => onChange({ filterableIndexPatterns: [], - doc: ({ - id: '1234', - title: 'My cool doc', - references: [], - state: { - query: 'kuery', - filters: [], - }, - } as unknown) as Document, + doc: defaultDoc, isSaveable: true, }) ); - instance.update(); - - const lastCall = - defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0]; + component.update(); + const lastCall = props.onAppLeave.mock.calls[props.onAppLeave.mock.calls.length - 1][0]; lastCall({ default: defaultLeave, confirm: confirmLeave }); - expect(defaultLeave).toHaveBeenCalled(); expect(confirmLeave).not.toHaveBeenCalled(); }); it('should confirm when the latest doc is invalid', async () => { - defaultArgs.editorFrame = frame; - instance = mount(); + const { component, frame, props } = mountWith({}); await act(async () => { - instance.setProps({ docId: '1234' }); + component.setProps({ initialInput: { savedObjectId: defaultSavedObjectId } }); }); - const onChange = frame.mount.mock.calls[0][1].onChange; act(() => onChange({ filterableIndexPatterns: [], - doc: ({ id: '1234', references: [] } as unknown) as Document, + doc: ({ savedObjectId: defaultSavedObjectId, references: [] } as unknown) as Document, isSaveable: true, }) ); - instance.update(); - - const lastCall = - defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0]; + component.update(); + const lastCall = props.onAppLeave.mock.calls[props.onAppLeave.mock.calls.length - 1][0]; lastCall({ default: defaultLeave, confirm: confirmLeave }); - expect(confirmLeave).toHaveBeenCalled(); expect(defaultLeave).not.toHaveBeenCalled(); }); diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index bfdf4ceaaabd3..d2ccbe0cb2fee 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -6,108 +6,82 @@ import _ from 'lodash'; import React, { useState, useEffect, useCallback } from 'react'; -import { I18nProvider } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { NavigationPublicPluginStart } from 'src/plugins/navigation/public'; -import { AppMountContext, AppMountParameters, NotificationsStart } from 'kibana/public'; -import { History } from 'history'; +import { NotificationsStart } from 'kibana/public'; import { EuiBreadcrumb } from '@elastic/eui'; -import { - Query, - DataPublicPluginStart, - syncQueryStateWithUrl, -} from '../../../../../src/plugins/data/public'; import { createKbnUrlStateStorage, - IStorageWrapper, withNotifyOnErrors, } from '../../../../../src/plugins/kibana_utils/public'; -import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; +import { useKibana } from '../../../../../src/plugins/kibana_react/public'; import { - SavedObjectSaveModalOrigin, OnSaveProps, checkForDuplicateTitle, + SavedObjectSaveModalOrigin, } from '../../../../../src/plugins/saved_objects/public'; -import { Document, SavedObjectStore, injectFilterReferences } from '../persistence'; -import { EditorFrameInstance } from '../types'; +import { injectFilterReferences } from '../persistence'; import { NativeRenderer } from '../native_renderer'; import { trackUiEvent } from '../lens_ui_telemetry'; import { esFilters, - Filter, IndexPattern as IndexPatternInstance, IndexPatternsContract, - SavedQuery, + syncQueryStateWithUrl, } from '../../../../../src/plugins/data/public'; -import { getFullPath } from '../../common'; - -interface State { - indicateNoData: boolean; - isLoading: boolean; - isSaveModalVisible: boolean; - indexPatternsForTopNav: IndexPatternInstance[]; - originatingApp?: string; - persistedDoc?: Document; - lastKnownDoc?: Document; - - // Properties needed to interface with TopNav - dateRange: { - fromDate: string; - toDate: string; - }; - query: Query; - filters: Filter[]; - savedQuery?: SavedQuery; - isSaveable: boolean; -} +import { LENS_EMBEDDABLE_TYPE, getFullPath } from '../../common'; +import { LensAppProps, LensAppServices, LensAppState } from './types'; +import { getLensTopNavConfig } from './lens_top_nav'; +import { + LensByReferenceInput, + LensEmbeddableInput, +} from '../editor_frame_service/embeddable/embeddable'; export function App({ - editorFrame, - data, - core, - storage, - docId, - docStorage, - redirectTo, - originatingApp, - navigation, + history, onAppLeave, + redirectTo, + editorFrame, + initialInput, + incomingState, + redirectToOrigin, setHeaderActionMenu, - history, - getAppNameFromId, -}: { - editorFrame: EditorFrameInstance; - data: DataPublicPluginStart; - navigation: NavigationPublicPluginStart; - core: AppMountContext['core']; - storage: IStorageWrapper; - docId?: string; - docStorage: SavedObjectStore; - redirectTo: (id?: string, returnToOrigin?: boolean, newlyCreated?: boolean) => void; - originatingApp?: string | undefined; - onAppLeave: AppMountParameters['onAppLeave']; - setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; - history: History; - getAppNameFromId?: (appId: string) => string | undefined; -}) { - const [state, setState] = useState(() => { +}: LensAppProps) { + const { + data, + chrome, + overlays, + navigation, + uiSettings, + application, + notifications, + attributeService, + savedObjectsClient, + getOriginatingAppName, + + // Temporarily required until the 'by value' paradigm is default. + dashboardFeatureFlag, + } = useKibana().services; + + const [state, setState] = useState(() => { const currentRange = data.query.timefilter.timefilter.getTime(); return { - isLoading: !!docId, - isSaveModalVisible: false, - indexPatternsForTopNav: [], query: data.query.queryString.getDefaultQuery(), + filters: data.query.filterManager.getFilters(), + isLoading: Boolean(initialInput), + indexPatternsForTopNav: [], dateRange: { fromDate: currentRange.from, toDate: currentRange.to, }, - originatingApp, - filters: data.query.filterManager.getFilters(), + isLinkedToOriginatingApp: Boolean(incomingState?.originatingApp), + isSaveModalVisible: false, indicateNoData: false, isSaveable: false, }; }); + const { lastKnownDoc } = state; + const showNoDataPopover = useCallback(() => { setState((prevState) => ({ ...prevState, indicateNoData: true })); }, [setState]); @@ -125,9 +99,44 @@ export function App({ state.indexPatternsForTopNav, ]); - const { lastKnownDoc } = state; + const onError = useCallback( + (e: { message: string }) => + notifications.toasts.addDanger({ + title: e.message, + }), + [notifications.toasts] + ); + + const getLastKnownDocWithoutPinnedFilters = useCallback( + function () { + if (!lastKnownDoc) return undefined; + const [pinnedFilters, appFilters] = _.partition( + injectFilterReferences(lastKnownDoc.state?.filters || [], lastKnownDoc.references), + esFilters.isFilterPinned + ); + return pinnedFilters?.length + ? { + ...lastKnownDoc, + state: { + ...lastKnownDoc.state, + filters: appFilters, + }, + } + : lastKnownDoc; + }, + [lastKnownDoc] + ); - const savingPermitted = state.isSaveable && core.application.capabilities.visualize.save; + const getIsByValueMode = useCallback( + () => + Boolean( + // Temporarily required until the 'by value' paradigm is default. + dashboardFeatureFlag.allowByValueEmbeddables && + state.isLinkedToOriginatingApp && + !(initialInput as LensByReferenceInput)?.savedObjectId + ), + [dashboardFeatureFlag.allowByValueEmbeddables, state.isLinkedToOriginatingApp, initialInput] + ); useEffect(() => { // Clear app-specific filters when navigating to Lens. Necessary because Lens @@ -156,8 +165,8 @@ export function App({ const kbnUrlStateStorage = createKbnUrlStateStorage({ history, - useHash: core.uiSettings.get('state:storeInSessionStorage'), - ...withNotifyOnErrors(core.notifications.toasts), + useHash: uiSettings.get('state:storeInSessionStorage'), + ...withNotifyOnErrors(notifications.toasts), }); const { stop: stopSyncingQueryServiceStateWithUrl } = syncQueryStateWithUrl( data.query, @@ -172,38 +181,18 @@ export function App({ }, [ data.query.filterManager, data.query.timefilter.timefilter, - core.notifications.toasts, - core.uiSettings, + notifications.toasts, + uiSettings, data.query, history, ]); - const getLastKnownDocWithoutPinnedFilters = useCallback( - function () { - if (!lastKnownDoc) return undefined; - const [pinnedFilters, appFilters] = _.partition( - injectFilterReferences(lastKnownDoc.state?.filters || [], lastKnownDoc.references), - esFilters.isFilterPinned - ); - return pinnedFilters?.length - ? { - ...lastKnownDoc, - state: { - ...lastKnownDoc.state, - filters: appFilters, - }, - } - : lastKnownDoc; - }, - [lastKnownDoc] - ); - useEffect(() => { onAppLeave((actions) => { // Confirm when the user has made any changes to an existing doc // or when the user has configured something without saving if ( - core.application.capabilities.visualize.save && + application.capabilities.visualize.save && !_.isEqual(state.persistedDoc?.state, getLastKnownDocWithoutPinnedFilters()?.state) && (state.isSaveable || state.persistedDoc) ) { @@ -220,379 +209,430 @@ export function App({ } }); }, [ - lastKnownDoc, onAppLeave, - state.persistedDoc, + lastKnownDoc, state.isSaveable, - core.application.capabilities.visualize.save, + state.persistedDoc, getLastKnownDocWithoutPinnedFilters, + application.capabilities.visualize.save, ]); // Sync Kibana breadcrumbs any time the saved document's title changes useEffect(() => { - core.chrome.setBreadcrumbs([ - ...(originatingApp && getAppNameFromId - ? [ - { - onClick: (e) => { - core.application.navigateToApp(originatingApp); - }, - text: getAppNameFromId(originatingApp), - } as EuiBreadcrumb, - ] - : []), - { - href: core.http.basePath.prepend(`/app/visualize#/`), + const isByValueMode = getIsByValueMode(); + const breadcrumbs: EuiBreadcrumb[] = []; + if (state.isLinkedToOriginatingApp && getOriginatingAppName() && redirectToOrigin) { + breadcrumbs.push({ + onClick: () => { + redirectToOrigin(); + }, + text: getOriginatingAppName(), + }); + } + if (!isByValueMode) { + breadcrumbs.push({ + href: application.getUrlForApp('visualize'), onClick: (e) => { - core.application.navigateToApp('visualize', { path: '/' }); + application.navigateToApp('visualize', { path: '/' }); e.preventDefault(); }, text: i18n.translate('xpack.lens.breadcrumbsTitle', { defaultMessage: 'Visualize', }), - }, - { - text: state.persistedDoc - ? state.persistedDoc.title - : i18n.translate('xpack.lens.breadcrumbsCreate', { defaultMessage: 'Create' }), - }, - ]); + }); + } + let currentDocTitle = i18n.translate('xpack.lens.breadcrumbsCreate', { + defaultMessage: 'Create', + }); + if (state.persistedDoc) { + currentDocTitle = isByValueMode + ? i18n.translate('xpack.lens.breadcrumbsByValue', { defaultMessage: 'Edit visualization' }) + : state.persistedDoc.title; + } + breadcrumbs.push({ text: currentDocTitle }); + chrome.setBreadcrumbs(breadcrumbs); }, [ - core.application, - core.chrome, - core.http.basePath, + dashboardFeatureFlag.allowByValueEmbeddables, + state.isLinkedToOriginatingApp, + getOriginatingAppName, state.persistedDoc, - originatingApp, - redirectTo, - getAppNameFromId, + redirectToOrigin, + getIsByValueMode, + initialInput, + application, + chrome, ]); - useEffect( - () => { - if (docId && (!state.persistedDoc || state.persistedDoc.id !== docId)) { - setState((s) => ({ ...s, isLoading: true })); - docStorage - .load(docId) - .then((doc) => { - core.chrome.recentlyAccessed.add(getFullPath(docId), doc.title, docId); - getAllIndexPatterns( - _.uniq( - doc.references.filter(({ type }) => type === 'index-pattern').map(({ id }) => id) - ), - data.indexPatterns, - core.notifications - ) - .then((indexPatterns) => { - // Don't overwrite any pinned filters - data.query.filterManager.setAppFilters( - injectFilterReferences(doc.state.filters, doc.references) - ); - setState((s) => ({ - ...s, - isLoading: false, - persistedDoc: doc, - lastKnownDoc: doc, - query: doc.state.query, - indexPatternsForTopNav: indexPatterns, - })); - }) - .catch((e) => { - setState((s) => ({ ...s, isLoading: false })); - - redirectTo(); - }); + useEffect(() => { + if ( + !initialInput || + (attributeService.inputIsRefType(initialInput) && + initialInput.savedObjectId === state.persistedDoc?.savedObjectId) + ) { + return; + } + + setState((s) => ({ ...s, isLoading: true })); + attributeService + .unwrapAttributes(initialInput) + .then((attributes) => { + if (!initialInput) { + return; + } + const doc = { + ...initialInput, + ...attributes, + type: LENS_EMBEDDABLE_TYPE, + }; + + if (attributeService.inputIsRefType(initialInput)) { + chrome.recentlyAccessed.add( + getFullPath(initialInput.savedObjectId), + attributes.title, + initialInput.savedObjectId + ); + } + getAllIndexPatterns( + _.uniq(doc.references.filter(({ type }) => type === 'index-pattern').map(({ id }) => id)), + data.indexPatterns, + notifications + ) + .then((indexPatterns) => { + // Don't overwrite any pinned filters + data.query.filterManager.setAppFilters( + injectFilterReferences(doc.state.filters, doc.references) + ); + setState((s) => ({ + ...s, + isLoading: false, + persistedDoc: doc, + lastKnownDoc: doc, + query: doc.state.query, + indexPatternsForTopNav: indexPatterns, + })); }) .catch((e) => { setState((s) => ({ ...s, isLoading: false })); - - core.notifications.toasts.addDanger( - i18n.translate('xpack.lens.app.docLoadingError', { - defaultMessage: 'Error loading saved document', - }) - ); - redirectTo(); }); - } - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [ - core.notifications, - data.indexPatterns, - data.query.filterManager, - docId, - // TODO: These dependencies are changing too often - // docStorage, - // redirectTo, - // state.persistedDoc, - ] - ); + }) + .catch((e) => { + setState((s) => ({ ...s, isLoading: false })); + notifications.toasts.addDanger( + i18n.translate('xpack.lens.app.docLoadingError', { + defaultMessage: 'Error loading saved document', + }) + ); + + redirectTo(); + }); + }, [ + notifications, + data.indexPatterns, + data.query.filterManager, + initialInput, + attributeService, + redirectTo, + chrome.recentlyAccessed, + state.persistedDoc?.savedObjectId, + state.persistedDoc?.state, + ]); const runSave = async ( saveProps: Omit & { returnToOrigin: boolean; onTitleDuplicate?: OnSaveProps['onTitleDuplicate']; newDescription?: string; - } + }, + options: { saveToLibrary: boolean } ) => { if (!lastKnownDoc) { return; } - - const doc = { + const docToSave = { ...getLastKnownDocWithoutPinnedFilters()!, description: saveProps.newDescription, - id: saveProps.newCopyOnSave ? undefined : lastKnownDoc.id, + savedObjectId: saveProps.newCopyOnSave ? undefined : lastKnownDoc.savedObjectId, title: saveProps.newTitle, }; - await checkForDuplicateTitle( - { - ...doc, - copyOnSave: saveProps.newCopyOnSave, - lastSavedTitle: lastKnownDoc?.title, - getEsType: () => 'lens', - getDisplayName: () => - i18n.translate('xpack.lens.app.saveModalType', { - defaultMessage: 'Lens visualization', - }), - }, - saveProps.isTitleDuplicateConfirmed, - saveProps.onTitleDuplicate, - { - savedObjectsClient: core.savedObjects.client, - overlays: core.overlays, + // Required to serialize filters in by value mode until + // https://github.com/elastic/kibana/issues/77588 is fixed + if (getIsByValueMode()) { + docToSave.state.filters.forEach((filter) => { + if (typeof filter.meta.value === 'function') { + delete filter.meta.value; + } + }); + } + + const originalInput = saveProps.newCopyOnSave ? undefined : initialInput; + const originalSavedObjectId = (originalInput as LensByReferenceInput)?.savedObjectId; + if (options.saveToLibrary && !originalInput) { + await checkForDuplicateTitle( + { + ...docToSave, + copyOnSave: saveProps.newCopyOnSave, + lastSavedTitle: lastKnownDoc.title, + getEsType: () => 'lens', + getDisplayName: () => + i18n.translate('xpack.lens.app.saveModalType', { + defaultMessage: 'Lens visualization', + }), + }, + saveProps.isTitleDuplicateConfirmed, + saveProps.onTitleDuplicate, + { + savedObjectsClient, + overlays, + } + ); + } + try { + const newInput = (await attributeService.wrapAttributes( + docToSave, + options.saveToLibrary, + originalInput + )) as LensEmbeddableInput; + + if (saveProps.returnToOrigin && redirectToOrigin) { + // disabling the validation on app leave because the document has been saved. + onAppLeave((actions) => { + return actions.default(); + }); + redirectToOrigin({ input: newInput, isCopied: saveProps.newCopyOnSave }); + return; } - ); - const newlyCreated: boolean = saveProps.newCopyOnSave || !lastKnownDoc?.id; - docStorage - .save(doc) - .then(({ id }) => { - core.chrome.recentlyAccessed.add(getFullPath(id), doc.title, id); - // Prevents unnecessary network request and disables save button - const newDoc = { ...doc, id }; - const currentOriginatingApp = state.originatingApp; + if ( + attributeService.inputIsRefType(newInput) && + newInput.savedObjectId !== originalSavedObjectId + ) { + chrome.recentlyAccessed.add( + getFullPath(newInput.savedObjectId), + docToSave.title, + newInput.savedObjectId + ); setState((s) => ({ ...s, isSaveModalVisible: false, - originatingApp: - newlyCreated && !saveProps.returnToOrigin ? undefined : currentOriginatingApp, - persistedDoc: newDoc, - lastKnownDoc: newDoc, + isLinkedToOriginatingApp: false, })); - if (docId !== id || saveProps.returnToOrigin) { - redirectTo(id, saveProps.returnToOrigin, newlyCreated); - } - }) - .catch((e) => { - // eslint-disable-next-line no-console - console.dir(e); - trackUiEvent('save_failed'); - core.notifications.toasts.addDanger( - i18n.translate('xpack.lens.app.docSavingError', { - defaultMessage: 'Error saving document', - }) - ); - setState((s) => ({ ...s, isSaveModalVisible: false })); - }); - }; + redirectTo(newInput.savedObjectId); + return; + } - const onError = useCallback( - (e: { message: string }) => - core.notifications.toasts.addDanger({ - title: e.message, - }), - [core.notifications.toasts] - ); + const newDoc = { + ...docToSave, + ...newInput, + }; + setState((s) => ({ + ...s, + persistedDoc: newDoc, + lastKnownDoc: newDoc, + isSaveModalVisible: false, + isLinkedToOriginatingApp: false, + })); + } catch (e) { + // eslint-disable-next-line no-console + console.dir(e); + trackUiEvent('save_failed'); + notifications.toasts.addDanger( + i18n.translate('xpack.lens.app.docSavingError', { + defaultMessage: 'Error saving document', + }) + ); + setState((s) => ({ ...s, isSaveModalVisible: false })); + } + }; const { TopNavMenu } = navigation.ui; + const savingPermitted = Boolean(state.isSaveable && application.capabilities.visualize.save); + const topNavConfig = getLensTopNavConfig({ + showSaveAndReturn: Boolean( + state.isLinkedToOriginatingApp && + // Temporarily required until the 'by value' paradigm is default. + (dashboardFeatureFlag.allowByValueEmbeddables || Boolean(initialInput)) + ), + isByValueMode: getIsByValueMode(), + showCancel: Boolean(state.isLinkedToOriginatingApp), + savingPermitted, + actions: { + saveAndReturn: () => { + if (savingPermitted && lastKnownDoc) { + // disabling the validation on app leave because the document has been saved. + onAppLeave((actions) => { + return actions.default(); + }); + runSave( + { + newTitle: lastKnownDoc.title, + newCopyOnSave: false, + isTitleDuplicateConfirmed: false, + returnToOrigin: true, + }, + { + saveToLibrary: + (initialInput && attributeService.inputIsRefType(initialInput)) ?? false, + } + ); + } + }, + showSaveModal: () => { + if (savingPermitted) { + setState((s) => ({ ...s, isSaveModalVisible: true })); + } + }, + cancel: () => { + if (redirectToOrigin) { + redirectToOrigin(); + } + }, + }, + }); + return ( - - -
-
- { - if (savingPermitted) { - runSave({ - newTitle: lastKnownDoc.title, - newCopyOnSave: false, - isTitleDuplicateConfirmed: false, - returnToOrigin: true, - }); - } - }, - testId: 'lnsApp_saveAndReturnButton', - disableButton: !savingPermitted, - }, - ] - : []), - { - label: - lastKnownDoc?.id && !!state.originatingApp - ? i18n.translate('xpack.lens.app.saveAs', { - defaultMessage: 'Save as', - }) - : i18n.translate('xpack.lens.app.save', { - defaultMessage: 'Save', - }), - emphasize: !state.originatingApp || !lastKnownDoc?.id, - run: () => { - if (savingPermitted) { - setState((s) => ({ ...s, isSaveModalVisible: true })); - } - }, - testId: 'lnsApp_saveButton', - disableButton: !savingPermitted, + <> +
+
+ { + const { dateRange, query } = payload; + if ( + dateRange.from !== state.dateRange.fromDate || + dateRange.to !== state.dateRange.toDate + ) { + data.query.timefilter.timefilter.setTime(dateRange); + trackUiEvent('app_date_change'); + } else { + trackUiEvent('app_query_change'); + } + setState((s) => ({ + ...s, + dateRange: { + fromDate: dateRange.from, + toDate: dateRange.to, }, - ]} - data-test-subj="lnsApp_topNav" - screenTitle={'lens'} - onQuerySubmit={(payload) => { - const { dateRange, query } = payload; + query: query || s.query, + })); + }} + onSaved={(savedQuery) => { + setState((s) => ({ ...s, savedQuery })); + }} + onSavedQueryUpdated={(savedQuery) => { + const savedQueryFilters = savedQuery.attributes.filters || []; + const globalFilters = data.query.filterManager.getGlobalFilters(); + data.query.filterManager.setFilters([...globalFilters, ...savedQueryFilters]); + setState((s) => ({ + ...s, + savedQuery: { ...savedQuery }, // Shallow query for reference issues + dateRange: savedQuery.attributes.timefilter + ? { + fromDate: savedQuery.attributes.timefilter.from, + toDate: savedQuery.attributes.timefilter.to, + } + : s.dateRange, + })); + }} + onClearSavedQuery={() => { + data.query.filterManager.setFilters(data.query.filterManager.getGlobalFilters()); + setState((s) => ({ + ...s, + savedQuery: undefined, + filters: data.query.filterManager.getGlobalFilters(), + query: data.query.queryString.getDefaultQuery(), + })); + }} + query={state.query} + dateRangeFrom={state.dateRange.fromDate} + dateRangeTo={state.dateRange.toDate} + indicateNoData={state.indicateNoData} + /> +
+ {(!state.isLoading || state.persistedDoc) && ( + { + if (isSaveable !== state.isSaveable) { + setState((s) => ({ ...s, isSaveable })); + } + if (!_.isEqual(state.persistedDoc, doc)) { + setState((s) => ({ ...s, lastKnownDoc: doc })); + } + // Update the cached index patterns if the user made a change to any of them if ( - dateRange.from !== state.dateRange.fromDate || - dateRange.to !== state.dateRange.toDate + state.indexPatternsForTopNav.length !== filterableIndexPatterns.length || + filterableIndexPatterns.some( + (id) => + !state.indexPatternsForTopNav.find((indexPattern) => indexPattern.id === id) + ) ) { - data.query.timefilter.timefilter.setTime(dateRange); - trackUiEvent('app_date_change'); - } else { - trackUiEvent('app_query_change'); + getAllIndexPatterns( + filterableIndexPatterns, + data.indexPatterns, + notifications + ).then((indexPatterns) => { + if (indexPatterns) { + setState((s) => ({ ...s, indexPatternsForTopNav: indexPatterns })); + } + }); } - - setState((s) => ({ - ...s, - dateRange: { - fromDate: dateRange.from, - toDate: dateRange.to, - }, - query: query || s.query, - })); - }} - appName={'lens'} - indexPatterns={state.indexPatternsForTopNav} - showSearchBar={true} - showDatePicker={true} - showQueryBar={true} - showFilterBar={true} - showSaveQuery={core.application.capabilities.visualize.saveQuery as boolean} - savedQuery={state.savedQuery} - onSaved={(savedQuery) => { - setState((s) => ({ ...s, savedQuery })); - }} - onSavedQueryUpdated={(savedQuery) => { - const savedQueryFilters = savedQuery.attributes.filters || []; - const globalFilters = data.query.filterManager.getGlobalFilters(); - data.query.filterManager.setFilters([...globalFilters, ...savedQueryFilters]); - setState((s) => ({ - ...s, - savedQuery: { ...savedQuery }, // Shallow query for reference issues - dateRange: savedQuery.attributes.timefilter - ? { - fromDate: savedQuery.attributes.timefilter.from, - toDate: savedQuery.attributes.timefilter.to, - } - : s.dateRange, - })); - }} - onClearSavedQuery={() => { - data.query.filterManager.setFilters(data.query.filterManager.getGlobalFilters()); - setState((s) => ({ - ...s, - savedQuery: undefined, - filters: data.query.filterManager.getGlobalFilters(), - query: data.query.queryString.getDefaultQuery(), - })); - }} - query={state.query} - dateRangeFrom={state.dateRange.fromDate} - dateRangeTo={state.dateRange.toDate} - indicateNoData={state.indicateNoData} - /> -
- - {(!state.isLoading || state.persistedDoc) && ( - { - if (isSaveable !== state.isSaveable) { - setState((s) => ({ ...s, isSaveable })); - } - if (!_.isEqual(state.persistedDoc, doc)) { - setState((s) => ({ ...s, lastKnownDoc: doc })); - } - - // Update the cached index patterns if the user made a change to any of them - if ( - state.indexPatternsForTopNav.length !== filterableIndexPatterns.length || - filterableIndexPatterns.some( - (id) => - !state.indexPatternsForTopNav.find((indexPattern) => indexPattern.id === id) - ) - ) { - getAllIndexPatterns( - filterableIndexPatterns, - data.indexPatterns, - core.notifications - ).then((indexPatterns) => { - if (indexPatterns) { - setState((s) => ({ ...s, indexPatternsForTopNav: indexPatterns })); - } - }); - } - }, - }} - /> - )} -
- {lastKnownDoc && state.isSaveModalVisible && ( - runSave(props)} - onClose={() => setState((s) => ({ ...s, isSaveModalVisible: false }))} - getAppNameFromId={getAppNameFromId} - documentInfo={{ - id: lastKnownDoc.id, - title: lastKnownDoc.title || '', - description: lastKnownDoc.description || '', + }, }} - objectType={i18n.translate('xpack.lens.app.saveModalType', { - defaultMessage: 'Lens visualization', - })} - data-test-subj="lnsApp_saveModalOrigin" /> )} - - +
+ {lastKnownDoc && state.isSaveModalVisible && ( + runSave(props, { saveToLibrary: true })} + onClose={() => { + setState((s) => ({ ...s, isSaveModalVisible: false })); + }} + getAppNameFromId={() => getOriginatingAppName()} + documentInfo={{ + id: lastKnownDoc.savedObjectId, + title: lastKnownDoc.title || '', + description: lastKnownDoc.description || '', + }} + returnToOriginSwitchLabel={ + getIsByValueMode() && initialInput + ? i18n.translate('xpack.lens.app.updatePanel', { + defaultMessage: 'Update panel on {originatingAppName}', + values: { originatingAppName: getOriginatingAppName() }, + }) + : undefined + } + objectType={i18n.translate('xpack.lens.app.saveModalType', { + defaultMessage: 'Lens visualization', + })} + data-test-subj="lnsApp_saveModalOrigin" + /> + )} + ); } diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx new file mode 100644 index 0000000000000..f6234d063d8cd --- /dev/null +++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { TopNavMenuData } from '../../../../../src/plugins/navigation/public'; +import { LensTopNavActions } from './types'; + +export function getLensTopNavConfig(options: { + showSaveAndReturn: boolean; + showCancel: boolean; + isByValueMode: boolean; + actions: LensTopNavActions; + savingPermitted: boolean; +}): TopNavMenuData[] { + const { showSaveAndReturn, showCancel, actions, savingPermitted } = options; + const topNavMenu: TopNavMenuData[] = []; + + const saveButtonLabel = options.isByValueMode + ? i18n.translate('xpack.lens.app.addToLibrary', { + defaultMessage: 'Save to library', + }) + : options.showSaveAndReturn + ? i18n.translate('xpack.lens.app.saveAs', { + defaultMessage: 'Save as', + }) + : i18n.translate('xpack.lens.app.save', { + defaultMessage: 'Save', + }); + + if (showSaveAndReturn) { + topNavMenu.push({ + label: i18n.translate('xpack.lens.app.saveAndReturn', { + defaultMessage: 'Save and return', + }), + emphasize: true, + iconType: 'check', + run: actions.saveAndReturn, + testId: 'lnsApp_saveAndReturnButton', + disableButton: !savingPermitted, + description: i18n.translate('xpack.lens.app.saveAndReturnButtonAriaLabel', { + defaultMessage: 'Save the current lens visualization and return to the last app', + }), + }); + } + + topNavMenu.push({ + label: saveButtonLabel, + emphasize: !showSaveAndReturn, + run: actions.showSaveModal, + testId: 'lnsApp_saveButton', + description: i18n.translate('xpack.lens.app.saveButtonAriaLabel', { + defaultMessage: 'Save the current lens visualization', + }), + disableButton: !savingPermitted, + }); + + if (showCancel) { + topNavMenu.push({ + label: i18n.translate('xpack.lens.app.cancel', { + defaultMessage: 'cancel', + }), + run: actions.cancel, + testId: 'lnsApp_cancelButton', + description: i18n.translate('xpack.lens.app.cancelButtonAriaLabel', { + defaultMessage: 'Return to the last app without saving changes', + }), + }); + } + return topNavMenu; +} diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index ebc38e4929f6c..0d50e541d3e48 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -11,6 +11,7 @@ import { HashRouter, Route, RouteComponentProps, Switch } from 'react-router-dom import { render, unmountComponentAtNode } from 'react-dom'; import { i18n } from '@kbn/i18n'; +import { DashboardFeatureFlagConfig } from 'src/plugins/dashboard/public'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import { LensReportManager, setReportManager, trackUiEvent } from '../lens_ui_telemetry'; @@ -18,76 +19,116 @@ import { LensReportManager, setReportManager, trackUiEvent } from '../lens_ui_te import { App } from './app'; import { EditorFrameStart } from '../types'; import { addHelpMenuToAppChrome } from '../help_menu_util'; -import { SavedObjectIndexStore } from '../persistence'; import { LensPluginStartDependencies } from '../plugin'; -import { LENS_EMBEDDABLE_TYPE } from '../../common'; +import { LENS_EMBEDDABLE_TYPE, LENS_EDIT_BY_VALUE } from '../../common'; +import { + LensEmbeddableInput, + LensByReferenceInput, + LensByValueInput, +} from '../editor_frame_service/embeddable/embeddable'; +import { LensAttributeService } from '../lens_attribute_service'; +import { LensAppServices, RedirectToOriginProps } from './types'; +import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; export async function mountApp( core: CoreSetup, params: AppMountParameters, - createEditorFrame: EditorFrameStart['createInstance'] + mountProps: { + createEditorFrame: EditorFrameStart['createInstance']; + getByValueFeatureFlag: () => Promise; + attributeService: LensAttributeService; + } ) { + const { createEditorFrame, getByValueFeatureFlag, attributeService } = mountProps; const [coreStart, startDependencies] = await core.getStartServices(); - const { data: dataStart, navigation, embeddable } = startDependencies; - const savedObjectsClient = coreStart.savedObjects.client; - addHelpMenuToAppChrome(coreStart.chrome, coreStart.docLinks); + const { data, navigation, embeddable } = startDependencies; + + const instance = await createEditorFrame(); + const storage = new Storage(localStorage); + const stateTransfer = embeddable?.getStateTransfer(params.history); + const embeddableEditorIncomingState = stateTransfer?.getIncomingEditorState(); + + const lensServices: LensAppServices = { + data, + storage, + navigation, + attributeService, + http: coreStart.http, + chrome: coreStart.chrome, + overlays: coreStart.overlays, + uiSettings: coreStart.uiSettings, + application: coreStart.application, + notifications: coreStart.notifications, + savedObjectsClient: coreStart.savedObjects.client, + getOriginatingAppName: () => { + return embeddableEditorIncomingState?.originatingApp + ? stateTransfer?.getAppNameFromId(embeddableEditorIncomingState.originatingApp) + : undefined; + }, + // Temporarily required until the 'by value' paradigm is default. + dashboardFeatureFlag: await getByValueFeatureFlag(), + }; + + addHelpMenuToAppChrome(coreStart.chrome, coreStart.docLinks); coreStart.chrome.docTitle.change( i18n.translate('xpack.lens.pageTitle', { defaultMessage: 'Lens' }) ); - const stateTransfer = embeddable?.getStateTransfer(params.history); - const { originatingApp } = - stateTransfer?.getIncomingEditorState({ keysToRemoveAfterFetch: ['originatingApp'] }) || {}; - - const instance = await createEditorFrame(); - setReportManager( new LensReportManager({ - storage: new Storage(localStorage), http: core.http, + storage, }) ); - const redirectTo = ( - routeProps: RouteComponentProps<{ id?: string }>, - id?: string, - returnToOrigin?: boolean, - newlyCreated?: boolean - ) => { - if (!id) { + + const getInitialInput = ( + routeProps: RouteComponentProps<{ id?: string }> + ): LensEmbeddableInput | undefined => { + if (routeProps.match.params.id) { + return { savedObjectId: routeProps.match.params.id } as LensByReferenceInput; + } + if (embeddableEditorIncomingState?.valueInput) { + return embeddableEditorIncomingState?.valueInput as LensByValueInput; + } + }; + + const redirectTo = (routeProps: RouteComponentProps<{ id?: string }>, savedObjectId?: string) => { + if (!savedObjectId) { routeProps.history.push('/'); - } else if (!originatingApp) { - routeProps.history.push(`/edit/${id}`); - } else if (!!originatingApp && id && returnToOrigin) { - routeProps.history.push(`/edit/${id}`); - - if (newlyCreated && stateTransfer) { - stateTransfer.navigateToWithEmbeddablePackage(originatingApp, { - state: { id, type: LENS_EMBEDDABLE_TYPE }, - }); - } else { - coreStart.application.navigateToApp(originatingApp); - } + } else { + routeProps.history.push(`/edit/${savedObjectId}`); } }; + const redirectToOrigin = (props?: RedirectToOriginProps) => { + if (!embeddableEditorIncomingState?.originatingApp) { + throw new Error('redirectToOrigin called without an originating app'); + } + if (stateTransfer && props?.input) { + const { input, isCopied } = props; + stateTransfer.navigateToWithEmbeddablePackage(embeddableEditorIncomingState?.originatingApp, { + state: { + embeddableId: isCopied ? undefined : embeddableEditorIncomingState.embeddableId, + type: LENS_EMBEDDABLE_TYPE, + input, + }, + }); + } else { + coreStart.application.navigateToApp(embeddableEditorIncomingState?.originatingApp); + } + }; + + // const featureFlagConfig = await getByValueFeatureFlag(); const renderEditor = (routeProps: RouteComponentProps<{ id?: string }>) => { trackUiEvent('loaded'); - return ( - redirectTo(routeProps, id, returnToOrigin, newlyCreated) - } - originatingApp={originatingApp} - getAppNameFromId={stateTransfer.getAppNameFromId} + initialInput={getInitialInput(routeProps)} + redirectTo={(savedObjectId?: string) => redirectTo(routeProps, savedObjectId)} + redirectToOrigin={redirectToOrigin} onAppLeave={params.onAppLeave} setHeaderActionMenu={params.setHeaderActionMenu} history={routeProps.history} @@ -103,13 +144,16 @@ export async function mountApp( params.element.classList.add('lnsAppWrapper'); render( - - - - - - - + + + + + + + + + + , params.element ); diff --git a/x-pack/plugins/lens/public/app_plugin/types.ts b/x-pack/plugins/lens/public/app_plugin/types.ts new file mode 100644 index 0000000000000..fcdd0b20f8d27 --- /dev/null +++ b/x-pack/plugins/lens/public/app_plugin/types.ts @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { History } from 'history'; +import { + ApplicationStart, + AppMountParameters, + ChromeStart, + HttpStart, + IUiSettingsClient, + NotificationsStart, + OverlayStart, + SavedObjectsStart, +} from '../../../../../src/core/public'; +import { + DataPublicPluginStart, + Filter, + IndexPattern, + Query, + SavedQuery, +} from '../../../../../src/plugins/data/public'; +import { Document } from '../persistence'; +import { LensEmbeddableInput } from '../editor_frame_service/embeddable/embeddable'; +import { NavigationPublicPluginStart } from '../../../../../src/plugins/navigation/public'; +import { LensAttributeService } from '../lens_attribute_service'; +import { IStorageWrapper } from '../../../../../src/plugins/kibana_utils/public'; +import { DashboardFeatureFlagConfig } from '../../../../../src/plugins/dashboard/public'; +import { EmbeddableEditorState } from '../../../../../src/plugins/embeddable/public'; +import { EditorFrameInstance } from '..'; + +export interface LensAppState { + isLoading: boolean; + persistedDoc?: Document; + lastKnownDoc?: Document; + isSaveModalVisible: boolean; + + // Used to show a popover that guides the user towards changing the date range when no data is available. + indicateNoData: boolean; + + // index patterns used to determine which filters are available in the top nav. + indexPatternsForTopNav: IndexPattern[]; + + // Determines whether the lens editor shows the 'save and return' button, and the originating app breadcrumb. + isLinkedToOriginatingApp?: boolean; + + // Properties needed to interface with TopNav + dateRange: { + fromDate: string; + toDate: string; + }; + query: Query; + filters: Filter[]; + savedQuery?: SavedQuery; + isSaveable: boolean; +} + +export interface RedirectToOriginProps { + input?: LensEmbeddableInput; + isCopied?: boolean; +} + +export interface LensAppProps { + history: History; + editorFrame: EditorFrameInstance; + onAppLeave: AppMountParameters['onAppLeave']; + setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; + redirectTo: (savedObjectId?: string) => void; + redirectToOrigin?: (props?: RedirectToOriginProps) => void; + + // The initial input passed in by the container when editing. Can be either by reference or by value. + initialInput?: LensEmbeddableInput; + + // State passed in by the container which is used to determine the id of the Originating App. + incomingState?: EmbeddableEditorState; +} + +export interface LensAppServices { + http: HttpStart; + chrome: ChromeStart; + overlays: OverlayStart; + storage: IStorageWrapper; + data: DataPublicPluginStart; + uiSettings: IUiSettingsClient; + application: ApplicationStart; + notifications: NotificationsStart; + navigation: NavigationPublicPluginStart; + attributeService: LensAttributeService; + savedObjectsClient: SavedObjectsStart['client']; + getOriginatingAppName: () => string | undefined; + + // Temporarily required until the 'by value' paradigm is default. + dashboardFeatureFlag: DashboardFeatureFlagConfig; +} + +export interface LensTopNavActions { + saveAndReturn: () => void; + showSaveModal: () => void; + cancel: () => void; +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/save.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/save.ts index 6da6d5a8c118f..4cb523f128a8c 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/save.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/save.ts @@ -59,7 +59,7 @@ export function getSavedObjectFormat({ return { doc: { - id: state.persistedId, + savedObjectId: state.persistedId, title: state.title, description: state.description, type: 'lens', diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.test.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.test.ts index c7f505aeca517..80d007e17f711 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.test.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.test.ts @@ -376,7 +376,7 @@ describe('editor_frame state management', () => { { type: 'VISUALIZATION_LOADED', doc: { - id: 'b', + savedObjectId: 'b', state: { datasourceStates: { a: { foo: 'c' } }, visualization: { bar: 'd' }, diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.ts index 09674ebf2ade2..fc8daaed059dd 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.ts @@ -156,7 +156,7 @@ export const reducer = (state: EditorFrameState, action: Action): EditorFrameSta case 'VISUALIZATION_LOADED': return { ...state, - persistedId: action.doc.id, + persistedId: action.doc.savedObjectId, title: action.doc.title, description: action.doc.description, datasourceStates: Object.entries(action.doc.state.datasourceStates).reduce( diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx index 1e2df28cad7b1..d48f9ed713caf 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx @@ -5,12 +5,29 @@ */ import { Subject } from 'rxjs'; -import { Embeddable } from './embeddable'; +import { + Embeddable, + LensByValueInput, + LensByReferenceInput, + LensSavedObjectAttributes, + LensEmbeddableInput, +} from './embeddable'; import { ReactExpressionRendererProps } from 'src/plugins/expressions/public'; -import { Query, TimeRange, Filter, TimefilterContract } from 'src/plugins/data/public'; +import { + Query, + TimeRange, + Filter, + TimefilterContract, + IndexPatternsContract, +} from 'src/plugins/data/public'; import { Document } from '../../persistence'; import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'; import { VIS_EVENT_TO_TRIGGER } from '../../../../../../src/plugins/visualizations/public/embeddable'; +import { coreMock, httpServiceMock } from '../../../../../../src/core/public/mocks'; +import { IBasePath } from '../../../../../../src/core/public'; +import { AttributeService } from '../../../../../../src/plugins/dashboard/public'; +import { Ast } from '@kbn/interpreter/common'; +import { LensAttributeService } from '../../lens_attribute_service'; jest.mock('../../../../../../src/plugins/inspector/public/', () => ({ isAvailable: false, @@ -29,61 +46,95 @@ const savedVis: Document = { visualizationType: '', }; +const attributeServiceMockFromSavedVis = (document: Document): LensAttributeService => { + const core = coreMock.createStart(); + const service = new AttributeService< + LensSavedObjectAttributes, + LensByValueInput, + LensByReferenceInput + >( + 'lens', + jest.fn(), + core.savedObjects.client, + core.overlays, + core.i18n.Context, + core.notifications.toasts + ); + service.unwrapAttributes = jest.fn((input: LensByValueInput | LensByReferenceInput) => { + return Promise.resolve({ ...document } as LensSavedObjectAttributes); + }); + service.wrapAttributes = jest.fn(); + return service; +}; + describe('embeddable', () => { let mountpoint: HTMLDivElement; let expressionRenderer: jest.Mock; let getTrigger: jest.Mock; let trigger: { exec: jest.Mock }; + let basePath: IBasePath; + let attributeService: AttributeService< + LensSavedObjectAttributes, + LensByValueInput, + LensByReferenceInput + >; beforeEach(() => { mountpoint = document.createElement('div'); expressionRenderer = jest.fn((_props) => null); trigger = { exec: jest.fn() }; getTrigger = jest.fn(() => trigger); + attributeService = attributeServiceMockFromSavedVis(savedVis); + const http = httpServiceMock.createSetupContract({ basePath: '/test' }); + basePath = http.basePath; }); afterEach(() => { mountpoint.remove(); }); - it('should render expression with expression renderer', () => { + it('should render expression with expression renderer', async () => { const embeddable = new Embeddable( - dataPluginMock.createSetupContract().query.timefilter.timefilter, - expressionRenderer, - getTrigger, { - editPath: '', - editUrl: '', + timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, + attributeService, + expressionRenderer, + basePath, + indexPatternService: {} as IndexPatternsContract, editable: true, - savedVis, - expression: 'my | expression', + getTrigger, + documentToExpression: () => Promise.resolve({} as Ast), + toExpressionString: () => 'my | expression', }, - { id: '123' } + {} as LensEmbeddableInput ); + await embeddable.initializeSavedVis({} as LensEmbeddableInput); embeddable.render(mountpoint); expect(expressionRenderer).toHaveBeenCalledTimes(1); expect(expressionRenderer.mock.calls[0][0]!.expression).toEqual('my | expression'); }); - it('should re-render if new input is pushed', () => { + it('should re-render if new input is pushed', async () => { const timeRange: TimeRange = { from: 'now-15d', to: 'now' }; const query: Query = { language: 'kquery', query: '' }; const filters: Filter[] = [{ meta: { alias: 'test', negate: false, disabled: false } }]; const embeddable = new Embeddable( - dataPluginMock.createSetupContract().query.timefilter.timefilter, - expressionRenderer, - getTrigger, { - editPath: '', - editUrl: '', + timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, + attributeService, + expressionRenderer, + basePath, + indexPatternService: {} as IndexPatternsContract, editable: true, - savedVis, - expression: 'my | expression', + getTrigger, + documentToExpression: () => Promise.resolve({} as Ast), + toExpressionString: () => 'my | expression', }, - { id: '123' } + { id: '123' } as LensEmbeddableInput ); + await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); embeddable.render(mountpoint); embeddable.updateInput({ @@ -95,61 +146,74 @@ describe('embeddable', () => { expect(expressionRenderer).toHaveBeenCalledTimes(2); }); - it('should pass context to embeddable', () => { + it('should pass context to embeddable', async () => { const timeRange: TimeRange = { from: 'now-15d', to: 'now' }; const query: Query = { language: 'kquery', query: '' }; const filters: Filter[] = [{ meta: { alias: 'test', negate: false, disabled: false } }]; + const input = { savedObjectId: '123', timeRange, query, filters } as LensEmbeddableInput; + const embeddable = new Embeddable( - dataPluginMock.createSetupContract().query.timefilter.timefilter, - expressionRenderer, - getTrigger, { - editPath: '', - editUrl: '', + timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, + attributeService, + expressionRenderer, + basePath, + indexPatternService: {} as IndexPatternsContract, editable: true, - savedVis, - expression: 'my | expression', + getTrigger, + documentToExpression: () => Promise.resolve({} as Ast), + toExpressionString: () => 'my | expression', }, - { id: '123', timeRange, query, filters } + input ); + await embeddable.initializeSavedVis(input); embeddable.render(mountpoint); - expect(expressionRenderer.mock.calls[0][0].searchContext).toEqual({ - timeRange, - query: [query, savedVis.state.query], - filters, - }); + expect(expressionRenderer.mock.calls[0][0].searchContext).toEqual( + expect.objectContaining({ + timeRange, + query: [query, savedVis.state.query], + filters, + }) + ); }); - it('should merge external context with query and filters of the saved object', () => { + it('should merge external context with query and filters of the saved object', async () => { const timeRange: TimeRange = { from: 'now-15d', to: 'now' }; const query: Query = { language: 'kquery', query: 'external filter' }; const filters: Filter[] = [{ meta: { alias: 'test', negate: false, disabled: false } }]; + const newSavedVis = { + ...savedVis, + state: { + ...savedVis.state, + query: { language: 'kquery', query: 'saved filter' }, + filters: [ + { meta: { alias: 'test', negate: false, disabled: false, indexRefName: 'filter-0' } }, + ], + }, + references: [{ type: 'index-pattern', name: 'filter-0', id: 'my-index-pattern-id' }], + }; + attributeService = attributeServiceMockFromSavedVis(newSavedVis); + + const input = { savedObjectId: '123', timeRange, query, filters } as LensEmbeddableInput; + const embeddable = new Embeddable( - dataPluginMock.createSetupContract().query.timefilter.timefilter, - expressionRenderer, - getTrigger, { - editPath: '', - editUrl: '', + timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, + attributeService, + expressionRenderer, + basePath, + indexPatternService: {} as IndexPatternsContract, editable: true, - savedVis: { - ...savedVis, - state: { - ...savedVis.state, - query: { language: 'kquery', query: 'saved filter' }, - filters: [ - { meta: { alias: 'test', negate: false, disabled: false, indexRefName: 'filter-0' } }, - ], - }, - references: [{ type: 'index-pattern', name: 'filter-0', id: 'my-index-pattern-id' }], - }, - expression: 'my | expression', + getTrigger, + documentToExpression: () => Promise.resolve({} as Ast), + toExpressionString: () => 'my | expression', }, - { id: '123', timeRange, query, filters } + input ); + await embeddable.initializeSavedVis(input); embeddable.render(mountpoint); expect(expressionRenderer.mock.calls[0][0].searchContext).toEqual({ @@ -163,20 +227,22 @@ describe('embeddable', () => { }); }); - it('should execute trigger on event from expression renderer', () => { + it('should execute trigger on event from expression renderer', async () => { const embeddable = new Embeddable( - dataPluginMock.createSetupContract().query.timefilter.timefilter, - expressionRenderer, - getTrigger, { - editPath: '', - editUrl: '', + timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, + attributeService, + expressionRenderer, + basePath, + indexPatternService: {} as IndexPatternsContract, editable: true, - savedVis, - expression: 'my | expression', + getTrigger, + documentToExpression: () => Promise.resolve({} as Ast), + toExpressionString: () => 'my | expression', }, - { id: '123' } + { id: '123' } as LensEmbeddableInput ); + await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); embeddable.render(mountpoint); const onEvent = expressionRenderer.mock.calls[0][0].onEvent!; @@ -190,24 +256,31 @@ describe('embeddable', () => { ); }); - it('should not re-render if only change is in disabled filter', () => { + it('should not re-render if only change is in disabled filter', async () => { const timeRange: TimeRange = { from: 'now-15d', to: 'now' }; const query: Query = { language: 'kquery', query: '' }; const filters: Filter[] = [{ meta: { alias: 'test', negate: false, disabled: true } }]; const embeddable = new Embeddable( - dataPluginMock.createSetupContract().query.timefilter.timefilter, - expressionRenderer, - getTrigger, { - editPath: '', - editUrl: '', + timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, + attributeService, + expressionRenderer, + basePath, + indexPatternService: {} as IndexPatternsContract, editable: true, - savedVis, - expression: 'my | expression', + getTrigger, + documentToExpression: () => Promise.resolve({} as Ast), + toExpressionString: () => 'my | expression', }, - { id: '123', timeRange, query, filters } + { id: '123', timeRange, query, filters } as LensEmbeddableInput ); + await embeddable.initializeSavedVis({ + id: '123', + timeRange, + query, + filters, + } as LensEmbeddableInput); embeddable.render(mountpoint); embeddable.updateInput({ @@ -219,7 +292,7 @@ describe('embeddable', () => { expect(expressionRenderer).toHaveBeenCalledTimes(1); }); - it('should re-render on auto refresh fetch observable', () => { + it('should re-render on auto refresh fetch observable', async () => { const timeRange: TimeRange = { from: 'now-15d', to: 'now' }; const query: Query = { language: 'kquery', query: '' }; const filters: Filter[] = [{ meta: { alias: 'test', negate: false, disabled: true } }]; @@ -230,18 +303,25 @@ describe('embeddable', () => { } as unknown) as TimefilterContract; const embeddable = new Embeddable( - timefilter, - expressionRenderer, - getTrigger, { - editPath: '', - editUrl: '', + timefilter, + attributeService, + expressionRenderer, + basePath, + indexPatternService: {} as IndexPatternsContract, editable: true, - savedVis, - expression: 'my | expression', + getTrigger, + documentToExpression: () => Promise.resolve({} as Ast), + toExpressionString: () => 'my | expression', }, - { id: '123', timeRange, query, filters } + { id: '123', timeRange, query, filters } as LensEmbeddableInput ); + await embeddable.initializeSavedVis({ + id: '123', + timeRange, + query, + filters, + } as LensEmbeddableInput); embeddable.render(mountpoint); autoRefreshFetchSubject.next(); diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx index 4df218a3e94e9..61a5d8cacdc4f 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx @@ -13,10 +13,12 @@ import { Query, TimefilterContract, TimeRange, + IndexPattern, } from 'src/plugins/data/public'; import { ExecutionContextSearch } from 'src/plugins/expressions'; import { Subscription } from 'rxjs'; +import { Ast } from '@kbn/interpreter/common'; import { ExpressionRendererEvent, ReactExpressionRendererType, @@ -28,41 +30,56 @@ import { EmbeddableInput, EmbeddableOutput, IContainer, + SavedObjectEmbeddableInput, + ReferenceOrValueEmbeddable, } from '../../../../../../src/plugins/embeddable/public'; import { DOC_TYPE, Document, injectFilterReferences } from '../../persistence'; import { ExpressionWrapper } from './expression_wrapper'; import { UiActionsStart } from '../../../../../../src/plugins/ui_actions/public'; import { isLensBrushEvent, isLensFilterEvent } from '../../types'; -export interface LensEmbeddableConfiguration { - expression: string | null; - savedVis: Document; - editUrl: string; - editPath: string; - editable: boolean; - indexPatterns?: IIndexPattern[]; -} +import { IndexPatternsContract } from '../../../../../../src/plugins/data/public'; +import { getEditPath } from '../../../common'; +import { IBasePath } from '../../../../../../src/core/public'; +import { LensAttributeService } from '../../lens_attribute_service'; -export interface LensEmbeddableInput extends EmbeddableInput { - timeRange?: TimeRange; - query?: Query; - filters?: Filter[]; -} +export type LensSavedObjectAttributes = Omit; + +export type LensByValueInput = { + attributes: LensSavedObjectAttributes; +} & EmbeddableInput; + +export type LensByReferenceInput = SavedObjectEmbeddableInput & EmbeddableInput; +export type LensEmbeddableInput = LensByValueInput | LensByReferenceInput; export interface LensEmbeddableOutput extends EmbeddableOutput { indexPatterns?: IIndexPattern[]; } -export class Embeddable extends AbstractEmbeddable { +export interface LensEmbeddableDeps { + attributeService: LensAttributeService; + documentToExpression: (doc: Document) => Promise; + toExpressionString: (astObj: Ast, type?: string) => string; + editable: boolean; + indexPatternService: IndexPatternsContract; + expressionRenderer: ReactExpressionRendererType; + timefilter: TimefilterContract; + basePath: IBasePath; + getTrigger?: UiActionsStart['getTrigger'] | undefined; +} + +export class Embeddable + extends AbstractEmbeddable + implements ReferenceOrValueEmbeddable { type = DOC_TYPE; private expressionRenderer: ReactExpressionRendererType; - private getTrigger: UiActionsStart['getTrigger'] | undefined; - private expression: string | null; - private savedVis: Document; + private savedVis: Document | undefined; + private expression: string | undefined | null; private domNode: HTMLElement | Element | undefined; private subscription: Subscription; private autoRefreshFetchSubscription: Subscription; + private isInitialized = false; private externalSearchContext: { timeRange?: TimeRange; @@ -72,50 +89,32 @@ export class Embeddable extends AbstractEmbeddable this.onContainerStateChanged(initialInput)); this.subscription = this.getInput$().subscribe((input) => this.onContainerStateChanged(input)); - this.onContainerStateChanged(initialInput); - this.autoRefreshFetchSubscription = timefilter + this.autoRefreshFetchSubscription = deps.timefilter .getAutoRefreshFetch$() .subscribe(this.reload.bind(this)); } public supportedTriggers() { + if (!this.savedVis) { + return []; + } switch (this.savedVis.visualizationType) { case 'lnsXY': return [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush]; @@ -128,6 +127,22 @@ export class Embeddable extends AbstractEmbeddable !filter.meta.disabled) @@ -144,9 +159,7 @@ export class Embeddable extends AbstractEmbeddable, @@ -173,6 +189,9 @@ export class Embeddable extends AbstractEmbeddable { - if (!this.getTrigger || this.input.disableTriggers) { + if (!this.deps.getTrigger || this.input.disableTriggers) { return; } if (isLensBrushEvent(event)) { - this.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec({ + this.deps.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec({ data: event.data, embeddable: this, }); } if (isLensFilterEvent(event)) { - this.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec({ + this.deps.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec({ data: event.data, embeddable: this, }); } }; - destroy() { - super.destroy(); - if (this.domNode) { - unmountComponentAtNode(this.domNode); - } - if (this.subscription) { - this.subscription.unsubscribe(); - } - this.autoRefreshFetchSubscription.unsubscribe(); - } - - reload() { + async reload() { const currentTime = Date.now(); if (this.externalSearchContext.lastReloadRequestTime !== currentTime) { this.externalSearchContext = { @@ -233,4 +241,68 @@ export class Embeddable extends AbstractEmbeddable type === 'index-pattern') + .map(async ({ id }) => { + try { + return await this.deps.indexPatternService.get(id); + } catch (error) { + // Unable to load index pattern, ignore error as the index patterns are only used to + // configure the filter and query bar - there is still a good chance to get the visualization + // to show. + return null; + } + }) + .filter((promise): promise is Promise => Boolean(promise)); + const indexPatterns = await Promise.all(promises); + // passing edit url and index patterns to the output of this embeddable for + // the container to pick them up and use them to configure filter bar and + // config dropdown correctly. + const input = this.getInput(); + const title = input.hidePanelTitles ? '' : input.title || this.savedVis.title; + const savedObjectId = (input as LensByReferenceInput).savedObjectId; + this.updateOutput({ + ...this.getOutput(), + defaultTitle: this.savedVis.title, + title, + editPath: getEditPath(savedObjectId), + editUrl: this.deps.basePath.prepend(`/app/lens${getEditPath(savedObjectId)}`), + indexPatterns, + }); + } + + public inputIsRefType = ( + input: LensByValueInput | LensByReferenceInput + ): input is LensByReferenceInput => { + return this.deps.attributeService.inputIsRefType(input); + }; + + public getInputAsRefType = async (): Promise => { + const input = this.deps.attributeService.getExplicitInputFromEmbeddable(this); + return this.deps.attributeService.getInputAsRefType(input, { + showSaveModal: true, + saveModalTitle: this.getTitle(), + }); + }; + + public getInputAsValueType = async (): Promise => { + const input = this.deps.attributeService.getExplicitInputFromEmbeddable(this); + return this.deps.attributeService.getInputAsValueType(input); + }; + + destroy() { + super.destroy(); + if (this.domNode) { + unmountComponentAtNode(this.domNode); + } + if (this.subscription) { + this.subscription.unsubscribe(); + } + this.autoRefreshFetchSubscription.unsubscribe(); + } } diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts index b8f9f8de1d286..8771d1ebaddb1 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts @@ -4,33 +4,30 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Capabilities, HttpSetup, SavedObjectsClientContract } from 'kibana/public'; +import { Capabilities, HttpSetup } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { RecursiveReadonly } from '@kbn/utility-types'; import { toExpression, Ast } from '@kbn/interpreter/target/common'; import { IndexPatternsContract, - IndexPattern, TimefilterContract, } from '../../../../../../src/plugins/data/public'; import { ReactExpressionRendererType } from '../../../../../../src/plugins/expressions/public'; import { EmbeddableFactoryDefinition, - ErrorEmbeddable, - EmbeddableInput, IContainer, } from '../../../../../../src/plugins/embeddable/public'; -import { Embeddable } from './embeddable'; -import { SavedObjectIndexStore, DOC_TYPE } from '../../persistence'; -import { getEditPath } from '../../../common'; +import { Embeddable, LensByReferenceInput, LensEmbeddableInput } from './embeddable'; +import { DOC_TYPE } from '../../persistence'; import { UiActionsStart } from '../../../../../../src/plugins/ui_actions/public'; import { Document } from '../../persistence/saved_object_store'; +import { LensAttributeService } from '../../lens_attribute_service'; -interface StartServices { +export interface LensEmbeddableStartServices { timefilter: TimefilterContract; coreHttp: HttpSetup; + attributeService: LensAttributeService; capabilities: RecursiveReadonly; - savedObjectsClient: SavedObjectsClientContract; expressionRenderer: ReactExpressionRendererType; indexPatternService: IndexPatternsContract; uiActions?: UiActionsStart; @@ -47,7 +44,7 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition { getIconForSavedObject: () => 'lensApp', }; - constructor(private getStartServices: () => Promise) {} + constructor(private getStartServices: () => Promise) {} public isEditable = async () => { const { capabilities } = await this.getStartServices(); @@ -66,59 +63,40 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition { createFromSavedObject = async ( savedObjectId: string, - input: Partial & { id: string }, + input: LensEmbeddableInput, parent?: IContainer ) => { + if (!(input as LensByReferenceInput).savedObjectId) { + (input as LensByReferenceInput).savedObjectId = savedObjectId; + } + return this.create(input, parent); + }; + + async create(input: LensEmbeddableInput, parent?: IContainer) { const { - savedObjectsClient, - coreHttp, - indexPatternService, timefilter, expressionRenderer, documentToExpression, uiActions, + coreHttp, + attributeService, + indexPatternService, } = await this.getStartServices(); - const store = new SavedObjectIndexStore(savedObjectsClient); - const savedVis = await store.load(savedObjectId); - - const promises = savedVis.references - .filter(({ type }) => type === 'index-pattern') - .map(async ({ id }) => { - try { - return await indexPatternService.get(id); - } catch (error) { - // Unable to load index pattern, ignore error as the index patterns are only used to - // configure the filter and query bar - there is still a good chance to get the visualization - // to show. - return null; - } - }); - const indexPatterns = ( - await Promise.all(promises) - ).filter((indexPattern: IndexPattern | null): indexPattern is IndexPattern => - Boolean(indexPattern) - ); - - const expression = await documentToExpression(savedVis); return new Embeddable( - timefilter, - expressionRenderer, - uiActions?.getTrigger, { - savedVis, - editPath: getEditPath(savedObjectId), - editUrl: coreHttp.basePath.prepend(`/app/lens${getEditPath(savedObjectId)}`), + attributeService, + indexPatternService, + timefilter, + expressionRenderer, editable: await this.isEditable(), - indexPatterns, - expression: expression ? toExpression(expression) : null, + basePath: coreHttp.basePath, + getTrigger: uiActions?.getTrigger, + documentToExpression, + toExpressionString: toExpression, }, input, parent ); - }; - - async create(input: EmbeddableInput) { - return new ErrorEmbeddable('Lens can only be created from a saved object', input); } } diff --git a/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx index 7b1d091c1c8fe..c1b6d74bb49c0 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx @@ -41,7 +41,8 @@ describe('editor_frame service', () => { (async () => { pluginInstance.setup( coreMock.createSetup() as CoreSetup, - pluginSetupDependencies + pluginSetupDependencies, + jest.fn() ); const publicAPI = pluginInstance.start(coreMock.createStart(), pluginStartDependencies); const instance = await publicAPI.createInstance(); @@ -61,7 +62,8 @@ describe('editor_frame service', () => { it('should not have child nodes after unmount', async () => { pluginInstance.setup( coreMock.createSetup() as CoreSetup, - pluginSetupDependencies + pluginSetupDependencies, + jest.fn() ); const publicAPI = pluginInstance.start(coreMock.createStart(), pluginStartDependencies); const instance = await publicAPI.createInstance(); diff --git a/x-pack/plugins/lens/public/editor_frame_service/service.tsx b/x-pack/plugins/lens/public/editor_frame_service/service.tsx index 5fc347179a032..bebc3e6989902 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/service.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/service.tsx @@ -25,10 +25,12 @@ import { Document } from '../persistence/saved_object_store'; import { EditorFrame } from './editor_frame'; import { mergeTables } from './merge_tables'; import { formatColumn } from './format_column'; -import { EmbeddableFactory } from './embeddable/embeddable_factory'; +import { EmbeddableFactory, LensEmbeddableStartServices } from './embeddable/embeddable_factory'; import { getActiveDatasourceIdFromDoc } from './editor_frame/state_management'; import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; +import { DashboardStart } from '../../../../../src/plugins/dashboard/public'; import { persistedStateToExpression } from './editor_frame/state_helpers'; +import { LensAttributeService } from '../lens_attribute_service'; export interface EditorFrameSetupPlugins { data: DataPublicPluginSetup; @@ -39,6 +41,7 @@ export interface EditorFrameSetupPlugins { export interface EditorFrameStartPlugins { data: DataPublicPluginStart; embeddable?: EmbeddableStart; + dashboard?: DashboardStart; expressions: ExpressionsStart; uiActions?: UiActionsStart; } @@ -78,16 +81,17 @@ export class EditorFrameService { public setup( core: CoreSetup, - plugins: EditorFrameSetupPlugins + plugins: EditorFrameSetupPlugins, + getAttributeService: () => LensAttributeService ): EditorFrameSetup { plugins.expressions.registerFunction(() => mergeTables); plugins.expressions.registerFunction(() => formatColumn); - const getStartServices = async () => { + const getStartServices = async (): Promise => { const [coreStart, deps] = await core.getStartServices(); return { + attributeService: getAttributeService(), capabilities: coreStart.application.capabilities, - savedObjectsClient: coreStart.savedObjects.client, coreHttp: coreStart.http, timefilter: deps.data.query.timefilter.timefilter, expressionRenderer: deps.expressions.ReactExpressionRenderer, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.tsx index 325f18ee9833a..3d692b1f7f5a8 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.tsx @@ -75,6 +75,10 @@ export function BucketNestingEditor({ defaultMessage: 'Top values for each {field}', values: { field: fieldName }, }), + range: i18n.translate('xpack.lens.indexPattern.groupingOverallRanges', { + defaultMessage: 'Top values for each {field}', + values: { field: fieldName }, + }), }; const bottomLevelCopy: Record = { @@ -90,6 +94,10 @@ export function BucketNestingEditor({ defaultMessage: 'Overall top {target}', values: { target: target.fieldName }, }), + range: i18n.translate('xpack.lens.indexPattern.groupingSecondRanges', { + defaultMessage: 'Overall top {target}', + values: { target: target.fieldName }, + }), }; return ( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx index 153757ac37da1..2f64a36e0462e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx @@ -332,7 +332,6 @@ export function DimensionEditor(props: DimensionEditorProps) { {!incompatibleSelectedOperationType && ParamEditor && ( <> - { if ( type === 'date' && @@ -180,7 +179,7 @@ export const dateHistogramOperation: OperationDefinition + <> {!intervalIsRestricted && ( )} - + ); }, }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx index cc1e23cb82a49..9985ad7229ecc 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx @@ -226,6 +226,7 @@ export const FilterList = ({ removeTitle={i18n.translate('xpack.lens.indexPattern.filters.removeFilter', { defaultMessage: 'Remove a filter', })} + isNotRemovable={localFilters.length === 1} > + range.label || + formatter.convert({ + gte: isValidNumber(range.from) ? range.from : FROM_PLACEHOLDER, + lt: isValidNumber(range.to) ? range.to : TO_PLACEHOLDER, + }); + +export const RangePopover = ({ + range, + setRange, + Button, + isOpenByCreation, + setIsOpenByCreation, +}: { + range: LocalRangeType; + setRange: (newRange: LocalRangeType) => void; + Button: React.FunctionComponent<{ onClick: MouseEventHandler }>; + isOpenByCreation: boolean; + setIsOpenByCreation: (open: boolean) => void; + formatter: IFieldFormat; +}) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const [tempRange, setTempRange] = useState(range); + + const saveRangeAndReset = (newRange: LocalRangeType, resetRange = false) => { + if (resetRange) { + // reset the temporary range for later use + setTempRange(range); + } + // send the range back to the main state + setRange(newRange); + }; + const { from, to } = tempRange; + + const lteAppendLabel = i18n.translate('xpack.lens.indexPattern.ranges.lessThanOrEqualAppend', { + defaultMessage: '\u2264', + }); + const lteTooltipContent = i18n.translate( + 'xpack.lens.indexPattern.ranges.lessThanOrEqualTooltip', + { + defaultMessage: 'Less than or equal to', + } + ); + const ltPrependLabel = i18n.translate('xpack.lens.indexPattern.ranges.lessThanPrepend', { + defaultMessage: '\u003c', + }); + const ltTooltipContent = i18n.translate('xpack.lens.indexPattern.ranges.lessThanTooltip', { + defaultMessage: 'Less than', + }); + + const onSubmit = () => { + setIsPopoverOpen(false); + setIsOpenByCreation(false); + saveRangeAndReset(tempRange, true); + }; + + return ( + { + setIsPopoverOpen((isOpen) => !isOpen); + setIsOpenByCreation(false); + }} + /> + } + data-test-subj="indexPattern-ranges-popover" + > + + + + { + const newRange = { + ...tempRange, + from: target.value !== '' ? Number(target.value) : -Infinity, + }; + setTempRange(newRange); + saveRangeAndReset(newRange); + }} + append={ + + {lteAppendLabel} + + } + fullWidth + compressed + placeholder={FROM_PLACEHOLDER} + isInvalid={!isValidRange(tempRange)} + /> + + + + + + { + const newRange = { + ...tempRange, + to: target.value !== '' ? Number(target.value) : -Infinity, + }; + setTempRange(newRange); + saveRangeAndReset(newRange); + }} + prepend={ + + {ltPrependLabel} + + } + fullWidth + compressed + placeholder={TO_PLACEHOLDER} + isInvalid={!isValidRange(tempRange)} + onKeyDown={({ key }: React.KeyboardEvent) => { + if (keys.ENTER === key && onSubmit) { + onSubmit(); + } + }} + /> + + + + + ); +}; + +export const AdvancedRangeEditor = ({ + ranges, + setRanges, + onToggleEditor, + formatter, +}: { + ranges: RangeTypeLens[]; + setRanges: (newRanges: RangeTypeLens[]) => void; + onToggleEditor: () => void; + formatter: IFieldFormat; +}) => { + // use a local state to store ids with range objects + const [localRanges, setLocalRanges] = useState(() => + ranges.map((range) => ({ ...range, id: generateId() })) + ); + // we need to force the open state of the popover from the outside in some scenarios + // so we need an extra state here + const [isOpenByCreation, setIsOpenByCreation] = useState(false); + + const lastIndex = localRanges.length - 1; + + // Update locally all the time, but bounce the parents prop function + // to aviod too many requests + useDebounce( + () => { + setRanges(localRanges.map(({ id, ...rest }) => ({ ...rest }))); + }, + TYPING_DEBOUNCE_TIME, + [localRanges] + ); + + const addNewRange = () => { + setLocalRanges([ + ...localRanges, + { + id: generateId(), + from: localRanges[localRanges.length - 1].to, + to: Infinity, + label: '', + }, + ]); + }; + + return ( + + + {' '} + {i18n.translate('xpack.lens.indexPattern.ranges.customIntervalsRemoval', { + defaultMessage: 'Remove custom intervals', + })} + + + } + > + <> + setIsOpenByCreation(false)} + droppableId="RANGES_DROPPABLE_AREA" + items={localRanges} + > + {localRanges.map((range: LocalRangeType, idx: number) => ( + { + const newRanges = localRanges.filter((_, i) => i !== idx); + setLocalRanges(newRanges); + }} + removeTitle={i18n.translate('xpack.lens.indexPattern.ranges.deleteRange', { + defaultMessage: 'Delete range', + })} + isNotRemovable={localRanges.length === 1} + > + { + const newRanges = [...localRanges]; + if (newRange.id === newRanges[idx].id) { + newRanges[idx] = newRange; + } else { + newRanges.push(newRange); + } + setLocalRanges(newRanges); + }} + formatter={formatter} + Button={({ onClick }: { onClick: MouseEventHandler }) => ( + + + {getBetterLabel(range, formatter)} + + + )} + /> + + ))} + + { + addNewRange(); + setIsOpenByCreation(true); + }} + label={i18n.translate('xpack.lens.indexPattern.ranges.addInterval', { + defaultMessage: 'Add interval', + })} + /> + + + ); +}; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/constants.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/constants.ts new file mode 100644 index 0000000000000..5c3c3c19a2b0f --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/constants.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const TYPING_DEBOUNCE_TIME = 256; +// Taken from the Visualize editor +export const FROM_PLACEHOLDER = '\u2212\u221E'; +export const TO_PLACEHOLDER = '+\u221E'; + +export const DEFAULT_INTERVAL = 1000; +export const AUTO_BARS = 'auto'; +export const MIN_HISTOGRAM_BARS = 1; +export const SLICES = 6; + +export const MODES = { + Range: 'range', + Histogram: 'histogram', +} as const; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/index.ts new file mode 100644 index 0000000000000..ccae0c949af0d --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './ranges'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx new file mode 100644 index 0000000000000..5d5acf7778973 --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx @@ -0,0 +1,175 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useEffect, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { useDebounce } from 'react-use'; +import { + EuiButtonEmpty, + EuiFormRow, + EuiRange, + EuiFlexItem, + EuiFlexGroup, + EuiButtonIcon, + EuiToolTip, +} from '@elastic/eui'; +import { IFieldFormat } from 'src/plugins/data/public'; +import { RangeColumnParams, UpdateParamsFnType, MODES_TYPES } from './ranges'; +import { AdvancedRangeEditor } from './advanced_editor'; +import { TYPING_DEBOUNCE_TIME, MODES, MIN_HISTOGRAM_BARS } from './constants'; + +const BaseRangeEditor = ({ + maxBars, + step, + maxHistogramBars, + onToggleEditor, + onMaxBarsChange, +}: { + maxBars: number; + step: number; + maxHistogramBars: number; + onToggleEditor: () => void; + onMaxBarsChange: (newMaxBars: number) => void; +}) => { + const [maxBarsValue, setMaxBarsValue] = useState(String(maxBars)); + + useDebounce( + () => { + onMaxBarsChange(Number(maxBarsValue)); + }, + TYPING_DEBOUNCE_TIME, + [maxBarsValue] + ); + + const granularityLabel = i18n.translate('xpack.lens.indexPattern.ranges.granularity', { + defaultMessage: 'Granularity', + }); + const decreaseButtonLabel = i18n.translate('xpack.lens.indexPattern.ranges.decreaseButtonLabel', { + defaultMessage: 'Decrease granularity', + }); + const increaseButtonLabel = i18n.translate('xpack.lens.indexPattern.ranges.increaseButtonLabel', { + defaultMessage: 'Increase granularity', + }); + + return ( + <> + + + + + + setMaxBarsValue('' + Math.max(Number(maxBarsValue) - step, MIN_HISTOGRAM_BARS)) + } + aria-label={decreaseButtonLabel} + /> + + + + setMaxBarsValue(currentTarget.value)} + /> + + + + + setMaxBarsValue('' + Math.min(Number(maxBarsValue) + step, maxHistogramBars)) + } + aria-label={increaseButtonLabel} + /> + + + + + + onToggleEditor()}> + {i18n.translate('xpack.lens.indexPattern.ranges.customIntervalsToggle', { + defaultMessage: 'Create custom intervals', + })} + + + ); +}; + +export const RangeEditor = ({ + setParam, + params, + maxHistogramBars, + maxBars, + granularityStep, + onChangeMode, + rangeFormatter, +}: { + params: RangeColumnParams; + maxHistogramBars: number; + maxBars: number; + granularityStep: number; + setParam: UpdateParamsFnType; + onChangeMode: (mode: MODES_TYPES) => void; + rangeFormatter: IFieldFormat; +}) => { + const [isAdvancedEditor, toggleAdvancedEditor] = useState(params.type === MODES.Range); + + // if the maxBars in the params is set to auto refresh it with the default value + // only on bootstrap + useEffect(() => { + if (params.maxBars !== maxBars) { + setParam('maxBars', maxBars); + } + }, [maxBars, params.maxBars, setParam]); + + if (isAdvancedEditor) { + return ( + { + setParam('ranges', ranges); + }} + onToggleEditor={() => { + onChangeMode(MODES.Histogram); + toggleAdvancedEditor(false); + }} + formatter={rangeFormatter} + /> + ); + } + + return ( + { + setParam('maxBars', newMaxBars); + }} + onToggleEditor={() => { + onChangeMode(MODES.Range); + toggleAdvancedEditor(true); + }} + /> + ); +}; 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 new file mode 100644 index 0000000000000..2409406afcdbc --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx @@ -0,0 +1,555 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; +import { act } from 'react-dom/test-utils'; +import { EuiFieldNumber, EuiRange, EuiButtonEmpty, EuiLink } from '@elastic/eui'; +import { IUiSettingsClient, SavedObjectsClientContract, HttpSetup } from 'kibana/public'; +import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; +import { IndexPatternPrivateState, IndexPattern } from '../../../types'; +import { dataPluginMock } from '../../../../../../../../src/plugins/data/public/mocks'; +import { rangeOperation } from '../index'; +import { RangeIndexPatternColumn } from './ranges'; +import { + MODES, + DEFAULT_INTERVAL, + TYPING_DEBOUNCE_TIME, + MIN_HISTOGRAM_BARS, + SLICES, +} from './constants'; +import { RangePopover } from './advanced_editor'; +import { DragDropBuckets } from '../shared_components'; + +const dataPluginMockValue = dataPluginMock.createStartContract(); +// need to overwrite the formatter field first +dataPluginMockValue.fieldFormats.deserialize = jest.fn().mockImplementation(() => { + return { convert: ({ gte, lt }: { gte: string; lt: string }) => `${gte} - ${lt}` }; +}); + +type ReactMouseEvent = React.MouseEvent & + React.MouseEvent; + +const defaultOptions = { + storage: {} as IStorageWrapper, + // need this for MAX_HISTOGRAM value + uiSettings: ({ + get: () => 100, + } as unknown) as IUiSettingsClient, + savedObjectsClient: {} as SavedObjectsClientContract, + dateRange: { + fromDate: 'now-1y', + toDate: 'now', + }, + data: dataPluginMockValue, + http: {} as HttpSetup, +}; + +describe('ranges', () => { + let state: IndexPatternPrivateState; + const InlineOptions = rangeOperation.paramEditor!; + const sourceField = 'MyField'; + const MAX_HISTOGRAM_VALUE = 100; + const GRANULARITY_DEFAULT_VALUE = (MAX_HISTOGRAM_VALUE - MIN_HISTOGRAM_BARS) / 2; + const GRANULARITY_STEP = (MAX_HISTOGRAM_VALUE - MIN_HISTOGRAM_BARS) / SLICES; + + function setToHistogramMode() { + const column = state.layers.first.columns.col1 as RangeIndexPatternColumn; + column.dataType = 'number'; + column.scale = 'interval'; + column.params.type = MODES.Histogram; + } + + function setToRangeMode() { + const column = state.layers.first.columns.col1 as RangeIndexPatternColumn; + column.dataType = 'string'; + column.scale = 'ordinal'; + column.params.type = MODES.Range; + } + + function getDefaultState(): IndexPatternPrivateState { + return { + indexPatternRefs: [], + indexPatterns: {}, + existingFields: {}, + currentIndexPatternId: '1', + isFirstExistenceFetch: false, + layers: { + first: { + indexPatternId: '1', + columnOrder: ['col1', 'col2'], + columns: { + // Start with the histogram type + col1: { + label: sourceField, + dataType: 'number', + operationType: 'range', + scale: 'interval', + isBucketed: true, + sourceField, + params: { + type: MODES.Histogram, + ranges: [{ from: 0, to: DEFAULT_INTERVAL, label: '' }], + maxBars: 'auto', + }, + }, + col2: { + label: 'Count', + dataType: 'number', + isBucketed: false, + sourceField: 'Records', + operationType: 'count', + }, + }, + }, + }, + }; + } + + beforeAll(() => { + jest.useFakeTimers(); + }); + + beforeEach(() => { + state = getDefaultState(); + }); + + describe('toEsAggConfig', () => { + afterAll(() => setToHistogramMode()); + + it('should reflect params correctly', () => { + const esAggsConfig = rangeOperation.toEsAggsConfig( + state.layers.first.columns.col1 as RangeIndexPatternColumn, + 'col1', + {} as IndexPattern + ); + expect(esAggsConfig).toEqual( + expect.objectContaining({ + type: MODES.Histogram, + params: expect.objectContaining({ + field: sourceField, + maxBars: null, + }), + }) + ); + }); + + it('should reflect the type correctly', () => { + setToRangeMode(); + + const esAggsConfig = rangeOperation.toEsAggsConfig( + state.layers.first.columns.col1 as RangeIndexPatternColumn, + 'col1', + {} as IndexPattern + ); + + expect(esAggsConfig).toEqual( + expect.objectContaining({ + type: MODES.Range, + }) + ); + }); + }); + + describe('getPossibleOperationForField', () => { + it('should return operation with the right type for number', () => { + expect( + rangeOperation.getPossibleOperationForField({ + aggregatable: true, + searchable: true, + name: 'test', + displayName: 'test', + type: 'number', + }) + ).toEqual({ + dataType: 'number', + isBucketed: true, + scale: 'interval', + }); + }); + + it('should not return operation if field type is not number', () => { + expect( + rangeOperation.getPossibleOperationForField({ + aggregatable: false, + searchable: true, + name: 'test', + displayName: 'test', + type: 'string', + }) + ).toEqual(undefined); + }); + }); + + describe('paramEditor', () => { + describe('Modify intervals in basic mode', () => { + beforeEach(() => { + state = getDefaultState(); + }); + + it('should start update the state with the default maxBars value', () => { + const setStateSpy = jest.fn(); + mount( + + ); + + expect(setStateSpy).toHaveBeenCalledWith({ + ...state, + layers: { + first: { + ...state.layers.first, + columns: { + ...state.layers.first.columns, + col1: { + ...state.layers.first.columns.col1, + params: { + ...state.layers.first.columns.col1.params, + maxBars: GRANULARITY_DEFAULT_VALUE, + }, + }, + }, + }, + }, + }); + }); + + it('should update state when changing Max bars number', () => { + const setStateSpy = jest.fn(); + + const instance = mount( + + ); + + act(() => { + instance.find(EuiRange).prop('onChange')!( + { + currentTarget: { + value: '' + MAX_HISTOGRAM_VALUE, + }, + } as React.ChangeEvent, + true + ); + jest.advanceTimersByTime(TYPING_DEBOUNCE_TIME * 4); + + expect(setStateSpy).toHaveBeenCalledWith({ + ...state, + layers: { + first: { + ...state.layers.first, + columns: { + ...state.layers.first.columns, + col1: { + ...state.layers.first.columns.col1, + params: { + ...state.layers.first.columns.col1.params, + maxBars: MAX_HISTOGRAM_VALUE, + }, + }, + }, + }, + }, + }); + }); + }); + + it('should update the state using the plus or minus buttons by the step amount', () => { + const setStateSpy = jest.fn(); + + const instance = mount( + + ); + + act(() => { + // minus button + instance + .find('[data-test-subj="lns-indexPattern-range-maxBars-minus"]') + .find('button') + .prop('onClick')!({} as ReactMouseEvent); + jest.advanceTimersByTime(TYPING_DEBOUNCE_TIME * 4); + + expect(setStateSpy).toHaveBeenCalledWith({ + ...state, + layers: { + first: { + ...state.layers.first, + columns: { + ...state.layers.first.columns, + col1: { + ...state.layers.first.columns.col1, + params: { + ...state.layers.first.columns.col1.params, + maxBars: GRANULARITY_DEFAULT_VALUE - GRANULARITY_STEP, + }, + }, + }, + }, + }, + }); + + // plus button + instance + .find('[data-test-subj="lns-indexPattern-range-maxBars-plus"]') + .find('button') + .prop('onClick')!({} as ReactMouseEvent); + jest.advanceTimersByTime(TYPING_DEBOUNCE_TIME * 4); + + expect(setStateSpy).toHaveBeenCalledWith({ + ...state, + layers: { + first: { + ...state.layers.first, + columns: { + ...state.layers.first.columns, + col1: { + ...state.layers.first.columns.col1, + params: { + ...state.layers.first.columns.col1.params, + maxBars: GRANULARITY_DEFAULT_VALUE, + }, + }, + }, + }, + }, + }); + }); + }); + }); + + describe('Specify range intervals manually', () => { + // @ts-expect-error + window['__react-beautiful-dnd-disable-dev-warnings'] = true; // issue with enzyme & react-beautiful-dnd throwing errors: https://github.com/atlassian/react-beautiful-dnd/issues/1593 + + beforeEach(() => setToRangeMode()); + + it('should show one range interval to start with', () => { + const setStateSpy = jest.fn(); + + const instance = mount( + + ); + + expect(instance.find(DragDropBuckets).children).toHaveLength(1); + }); + + it('should add a new range', () => { + const setStateSpy = jest.fn(); + + const instance = mount( + + ); + + // This series of act clojures are made to make it work properly the update flush + act(() => { + instance.find(EuiButtonEmpty).prop('onClick')!({} as ReactMouseEvent); + }); + + act(() => { + // need another wrapping for this in order to work + instance.update(); + + expect(instance.find(RangePopover)).toHaveLength(2); + + // edit the range and check + instance.find(RangePopover).find(EuiFieldNumber).first().prop('onChange')!({ + target: { + value: '50', + }, + } as React.ChangeEvent); + jest.advanceTimersByTime(TYPING_DEBOUNCE_TIME * 4); + + expect(setStateSpy).toHaveBeenCalledWith({ + ...state, + layers: { + first: { + ...state.layers.first, + columns: { + ...state.layers.first.columns, + col1: { + ...state.layers.first.columns.col1, + params: { + ...state.layers.first.columns.col1.params, + ranges: [ + { from: 0, to: DEFAULT_INTERVAL, label: '' }, + { from: 50, to: Infinity, label: '' }, + ], + }, + }, + }, + }, + }, + }); + }); + }); + + it('should open a popover to edit an existing range', () => { + const setStateSpy = jest.fn(); + + const instance = mount( + + ); + + // This series of act clojures are made to make it work properly the update flush + act(() => { + instance.find(RangePopover).find(EuiLink).prop('onClick')!({} as ReactMouseEvent); + }); + + act(() => { + // need another wrapping for this in order to work + instance.update(); + + // edit the range "to" field + instance.find(RangePopover).find(EuiFieldNumber).last().prop('onChange')!({ + target: { + value: '50', + }, + } as React.ChangeEvent); + jest.advanceTimersByTime(TYPING_DEBOUNCE_TIME * 4); + + expect(setStateSpy).toHaveBeenCalledWith({ + ...state, + layers: { + first: { + ...state.layers.first, + columns: { + ...state.layers.first.columns, + col1: { + ...state.layers.first.columns.col1, + params: { + ...state.layers.first.columns.col1.params, + ranges: [{ from: 0, to: 50, label: '' }], + }, + }, + }, + }, + }, + }); + }); + }); + + it('should not accept invalid ranges', () => { + const setStateSpy = jest.fn(); + + const instance = mount( + + ); + + // This series of act clojures are made to make it work properly the update flush + act(() => { + instance.find(RangePopover).find(EuiLink).prop('onClick')!({} as ReactMouseEvent); + }); + + act(() => { + // need another wrapping for this in order to work + instance.update(); + + // edit the range "to" field + instance.find(RangePopover).find(EuiFieldNumber).last().prop('onChange')!({ + target: { + value: '-1', + }, + } as React.ChangeEvent); + }); + + act(() => { + instance.update(); + + // and check + expect(instance.find(RangePopover).find(EuiFieldNumber).last().prop('isInvalid')).toBe( + true + ); + }); + }); + + it('should be possible to remove a range if multiple', () => { + const setStateSpy = jest.fn(); + + // Add an extra range + (state.layers.first.columns.col1 as RangeIndexPatternColumn).params.ranges.push({ + from: DEFAULT_INTERVAL, + to: 2 * DEFAULT_INTERVAL, + label: '', + }); + + const instance = mount( + + ); + + expect(instance.find(RangePopover)).toHaveLength(2); + + // This series of act closures are made to make it work properly the update flush + act(() => { + instance + .find('[data-test-subj="lns-customBucketContainer-remove"]') + .last() + .prop('onClick')!({} as ReactMouseEvent); + }); + + act(() => { + // need another wrapping for this in order to work + instance.update(); + + expect(instance.find(RangePopover)).toHaveLength(1); + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx new file mode 100644 index 0000000000000..530c2e962759b --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx @@ -0,0 +1,199 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; + +import { UI_SETTINGS } from '../../../../../../../../src/plugins/data/common'; +import { Range } from '../../../../../../../../src/plugins/expressions/common/expression_types/index'; +import { RangeEditor } from './range_editor'; +import { OperationDefinition } from '../index'; +import { FieldBasedIndexPatternColumn } from '../column_types'; +import { updateColumnParam, changeColumn } from '../../../state_helpers'; +import { MODES, AUTO_BARS, DEFAULT_INTERVAL, MIN_HISTOGRAM_BARS, SLICES } from './constants'; + +type RangeType = Omit; +export type RangeTypeLens = RangeType & { label: string }; + +export type MODES_TYPES = typeof MODES[keyof typeof MODES]; + +export interface RangeIndexPatternColumn extends FieldBasedIndexPatternColumn { + operationType: 'range'; + params: { + type: MODES_TYPES; + maxBars: typeof AUTO_BARS | number; + ranges: RangeTypeLens[]; + }; +} + +export type RangeColumnParams = RangeIndexPatternColumn['params']; +export type UpdateParamsFnType = ( + paramName: K, + value: RangeColumnParams[K] +) => void; + +export const isValidNumber = (value: number | '') => + value !== '' && !isNaN(value) && isFinite(value); +export const isRangeWithin = (range: RangeTypeLens): boolean => range.from <= range.to; +const isFullRange = ({ from, to }: RangeType) => isValidNumber(from) && isValidNumber(to); +export const isValidRange = (range: RangeTypeLens): boolean => { + if (isFullRange(range)) { + return isRangeWithin(range); + } + return true; +}; + +function getEsAggsParams({ sourceField, params }: RangeIndexPatternColumn) { + if (params.type === MODES.Range) { + return { + field: sourceField, + ranges: params.ranges.filter(isValidRange).map>((range) => { + if (isFullRange(range)) { + return { from: range.from, to: range.to }; + } + const partialRange: Partial = {}; + // be careful with the fields to set on partial ranges + if (isValidNumber(range.from)) { + partialRange.from = range.from; + } + if (isValidNumber(range.to)) { + partialRange.to = range.to; + } + return partialRange; + }), + }; + } + return { + field: sourceField, + // fallback to 0 in case of empty string + maxBars: params.maxBars === AUTO_BARS ? null : params.maxBars, + has_extended_bounds: false, + min_doc_count: 0, + extended_bounds: { min: '', max: '' }, + }; +} + +export const rangeOperation: OperationDefinition = { + type: 'range', + displayName: i18n.translate('xpack.lens.indexPattern.ranges', { + defaultMessage: 'Ranges', + }), + priority: 4, // Higher than terms, so numbers get histogram + getPossibleOperationForField: ({ aggregationRestrictions, aggregatable, type }) => { + if ( + type === 'number' && + aggregatable && + (!aggregationRestrictions || aggregationRestrictions.range) + ) { + return { + dataType: 'number', + isBucketed: true, + scale: 'interval', + }; + } + }, + buildColumn({ suggestedPriority, field }) { + return { + label: field.name, + dataType: 'number', // string for Range + operationType: 'range', + suggestedPriority, + sourceField: field.name, + isBucketed: true, + scale: 'interval', // ordinal for Range + params: { + type: MODES.Histogram, + ranges: [{ from: 0, to: DEFAULT_INTERVAL, label: '' }], + maxBars: AUTO_BARS, + }, + }; + }, + isTransferable: (column, newIndexPattern) => { + const newField = newIndexPattern.fields.find((field) => field.name === column.sourceField); + + return Boolean( + newField && + newField.type === 'number' && + newField.aggregatable && + (!newField.aggregationRestrictions || newField.aggregationRestrictions.range) + ); + }, + onFieldChange: (oldColumn, indexPattern, field) => { + return { + ...oldColumn, + label: field.name, + sourceField: field.name, + }; + }, + toEsAggsConfig: (column, columnId) => { + const params = getEsAggsParams(column); + return { + id: columnId, + enabled: true, + type: column.params.type, + schema: 'segment', + params, + }; + }, + paramEditor: ({ state, setState, currentColumn, layerId, columnId, uiSettings, data }) => { + const rangeFormatter = data.fieldFormats.deserialize({ id: 'range' }); + const MAX_HISTOGRAM_BARS = uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS); + const granularityStep = (MAX_HISTOGRAM_BARS - MIN_HISTOGRAM_BARS) / SLICES; + const maxBarsDefaultValue = (MAX_HISTOGRAM_BARS - MIN_HISTOGRAM_BARS) / 2; + + // Used to change one param at the time + const setParam: UpdateParamsFnType = (paramName, value) => { + setState( + updateColumnParam({ + state, + layerId, + currentColumn, + paramName, + value, + }) + ); + }; + + // Useful to change more params at once + const onChangeMode = (newMode: MODES_TYPES) => { + const scale = newMode === MODES.Range ? 'ordinal' : 'interval'; + const dataType = newMode === MODES.Range ? 'string' : 'number'; + setState( + changeColumn({ + state, + layerId, + columnId, + newColumn: { + ...currentColumn, + scale, + dataType, + params: { + type: newMode, + ranges: [{ from: 0, to: DEFAULT_INTERVAL, label: '' }], + maxBars: maxBarsDefaultValue, + }, + }, + keepParams: false, + }) + ); + }; + return ( + + ); + }, +}; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/buckets.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/buckets.tsx index 73378cea919a6..47380f7865578 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/buckets.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/buckets.tsx @@ -35,6 +35,7 @@ interface BucketContainerProps { invalidMessage: string; onRemoveClick: () => void; removeTitle: string; + isNotRemovable?: boolean; children: React.ReactNode; dataTestSubj?: string; } @@ -46,6 +47,7 @@ const BucketContainer = ({ removeTitle, children, dataTestSubj, + isNotRemovable, }: BucketContainerProps) => { return ( @@ -75,6 +77,7 @@ const BucketContainer = ({ onClick={onRemoveClick} aria-label={removeTitle} title={removeTitle} + disabled={isNotRemovable} />
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx index 20c421008a746..c1a87a2013747 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiForm, EuiFormRow, EuiRange, EuiSelect } from '@elastic/eui'; +import { EuiFormRow, EuiRange, EuiSelect } from '@elastic/eui'; import { IndexPatternColumn } from '../../indexpattern'; import { updateColumnParam } from '../../state_helpers'; import { DataType } from '../../../types'; @@ -171,7 +171,7 @@ export const termsOperation: OperationDefinition = { }), }); return ( - + <> = { })} /> - + ); }, }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts index 4ac3fc89500f9..703431f724c5d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts @@ -225,29 +225,43 @@ describe('getOperationTypesForField', () => { it('should list out all field-operation tuples for different operation meta data', () => { expect(getAvailableOperationsByMetadata(expectedIndexPatterns[1])).toMatchInlineSnapshot(` Array [ + Object { + "operationMetaData": Object { + "dataType": "date", + "isBucketed": true, + "scale": "interval", + }, + "operations": Array [ + Object { + "field": "timestamp", + "operationType": "date_histogram", + "type": "field", + }, + ], + }, Object { "operationMetaData": Object { "dataType": "number", "isBucketed": true, - "scale": "ordinal", + "scale": "interval", }, "operations": Array [ Object { "field": "bytes", - "operationType": "terms", + "operationType": "range", "type": "field", }, ], }, Object { "operationMetaData": Object { - "dataType": "string", + "dataType": "number", "isBucketed": true, "scale": "ordinal", }, "operations": Array [ Object { - "field": "source", + "field": "bytes", "operationType": "terms", "type": "field", }, @@ -255,14 +269,14 @@ describe('getOperationTypesForField', () => { }, Object { "operationMetaData": Object { - "dataType": "date", + "dataType": "string", "isBucketed": true, - "scale": "interval", + "scale": "ordinal", }, "operations": Array [ Object { - "field": "timestamp", - "operationType": "date_histogram", + "field": "source", + "operationType": "terms", "type": "field", }, ], diff --git a/x-pack/plugins/lens/public/lens_attribute_service.ts b/x-pack/plugins/lens/public/lens_attribute_service.ts new file mode 100644 index 0000000000000..3c43fd98cceb4 --- /dev/null +++ b/x-pack/plugins/lens/public/lens_attribute_service.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreStart } from '../../../../src/core/public'; +import { LensPluginStartDependencies } from './plugin'; +import { AttributeService } from '../../../../src/plugins/dashboard/public'; +import { + LensSavedObjectAttributes, + LensByValueInput, + LensByReferenceInput, +} from './editor_frame_service/embeddable/embeddable'; +import { SavedObjectIndexStore, DOC_TYPE } from './persistence'; + +export type LensAttributeService = AttributeService< + LensSavedObjectAttributes, + LensByValueInput, + LensByReferenceInput +>; + +export function getLensAttributeService( + core: CoreStart, + startDependencies: LensPluginStartDependencies +): LensAttributeService { + const savedObjectStore = new SavedObjectIndexStore(core.savedObjects.client); + return startDependencies.dashboard.getAttributeService< + LensSavedObjectAttributes, + LensByValueInput, + LensByReferenceInput + >(DOC_TYPE, { + customSaveMethod: async ( + type: string, + attributes: LensSavedObjectAttributes, + savedObjectId?: string + ) => { + const savedDoc = await savedObjectStore.save({ + ...attributes, + savedObjectId, + type: DOC_TYPE, + }); + return { id: savedDoc.savedObjectId }; + }, + customUnwrapMethod: (savedObject) => { + return { + ...savedObject.attributes, + references: savedObject.references, + }; + }, + }); +} diff --git a/x-pack/plugins/lens/public/lens_ui_telemetry/factory.test.ts b/x-pack/plugins/lens/public/lens_ui_telemetry/factory.test.ts index 8bb1e086a37c2..fa7747dd18e42 100644 --- a/x-pack/plugins/lens/public/lens_ui_telemetry/factory.test.ts +++ b/x-pack/plugins/lens/public/lens_ui_telemetry/factory.test.ts @@ -83,7 +83,7 @@ describe('Lens UI telemetry', () => { jest.runOnlyPendingTimers(); - expect(http.post).toHaveBeenCalledWith(`/api/lens/telemetry`, { + expect(http.post).toHaveBeenCalledWith(`/api/lens/stats`, { body: JSON.stringify({ events: { '2019-10-23': { diff --git a/x-pack/plugins/lens/public/lens_ui_telemetry/factory.ts b/x-pack/plugins/lens/public/lens_ui_telemetry/factory.ts index cb517acff4f7a..8f9ce7f2ceab8 100644 --- a/x-pack/plugins/lens/public/lens_ui_telemetry/factory.ts +++ b/x-pack/plugins/lens/public/lens_ui_telemetry/factory.ts @@ -86,7 +86,7 @@ export class LensReportManager { this.readFromStorage(); if (Object.keys(this.events).length || Object.keys(this.suggestionEvents).length) { try { - await this.http.post(`${BASE_API_URL}/telemetry`, { + await this.http.post(`${BASE_API_URL}/stats`, { body: JSON.stringify({ events: this.events, suggestionEvents: this.suggestionEvents, diff --git a/x-pack/plugins/lens/public/persistence/saved_object_store.test.ts b/x-pack/plugins/lens/public/persistence/saved_object_store.test.ts index ba7c0ee6ae786..6b6f81aeefed0 100644 --- a/x-pack/plugins/lens/public/persistence/saved_object_store.test.ts +++ b/x-pack/plugins/lens/public/persistence/saved_object_store.test.ts @@ -42,7 +42,7 @@ describe('LensStore', () => { }); expect(doc).toEqual({ - id: 'FOO', + savedObjectId: 'FOO', title: 'Hello', description: 'My doc', visualizationType: 'bar', @@ -82,7 +82,7 @@ describe('LensStore', () => { test('updates and returns a visualization document', async () => { const { client, store } = testStore(); const doc = await store.save({ - id: 'Gandalf', + savedObjectId: 'Gandalf', title: 'Even the very wise cannot see all ends.', visualizationType: 'line', references: [], @@ -95,7 +95,7 @@ describe('LensStore', () => { }); expect(doc).toEqual({ - id: 'Gandalf', + savedObjectId: 'Gandalf', title: 'Even the very wise cannot see all ends.', visualizationType: 'line', references: [], diff --git a/x-pack/plugins/lens/public/persistence/saved_object_store.ts b/x-pack/plugins/lens/public/persistence/saved_object_store.ts index e4609213ec792..c6b3fd2cc0f65 100644 --- a/x-pack/plugins/lens/public/persistence/saved_object_store.ts +++ b/x-pack/plugins/lens/public/persistence/saved_object_store.ts @@ -13,7 +13,7 @@ import { Query } from '../../../../../src/plugins/data/public'; import { PersistableFilter } from '../../common'; export interface Document { - id?: string; + savedObjectId?: string; type?: string; visualizationType: string | null; title: string; @@ -30,11 +30,11 @@ export interface Document { export const DOC_TYPE = 'lens'; export interface DocumentSaver { - save: (vis: Document) => Promise<{ id: string }>; + save: (vis: Document) => Promise<{ savedObjectId: string }>; } export interface DocumentLoader { - load: (id: string) => Promise; + load: (savedObjectId: string) => Promise; } export type SavedObjectStore = DocumentLoader & DocumentSaver; @@ -46,20 +46,20 @@ export class SavedObjectIndexStore implements SavedObjectStore { this.client = client; } - async save(vis: Document) { - const { id, type, references, ...rest } = vis; + save = async (vis: Document) => { + const { savedObjectId, type, references, ...rest } = vis; // TODO: SavedObjectAttributes should support this kind of object, // remove this workaround when SavedObjectAttributes is updated. const attributes = (rest as unknown) as SavedObjectAttributes; - const result = await (id - ? this.safeUpdate(id, attributes, references) + const result = await (savedObjectId + ? this.safeUpdate(savedObjectId, attributes, references) : this.client.create(DOC_TYPE, attributes, { references, })); - return { ...vis, id: result.id }; - } + return { ...vis, savedObjectId: result.id }; + }; // As Lens is using an object to store its attributes, using the update API // will merge the new attribute object with the old one, not overwriting deleted @@ -68,7 +68,7 @@ export class SavedObjectIndexStore implements SavedObjectStore { // This function fixes this by doing two updates - one to empty out the document setting // every key to null, and a second one to load the new content. private async safeUpdate( - id: string, + savedObjectId: string, attributes: SavedObjectAttributes, references: SavedObjectReference[] ) { @@ -78,14 +78,14 @@ export class SavedObjectIndexStore implements SavedObjectStore { }); return ( await this.client.bulkUpdate([ - { type: DOC_TYPE, id, attributes: resetAttributes, references }, - { type: DOC_TYPE, id, attributes, references }, + { type: DOC_TYPE, id: savedObjectId, attributes: resetAttributes, references }, + { type: DOC_TYPE, id: savedObjectId, attributes, references }, ]) ).savedObjects[1]; } - async load(id: string): Promise { - const { type, attributes, references, error } = await this.client.get(DOC_TYPE, id); + async load(savedObjectId: string): Promise { + const { type, attributes, references, error } = await this.client.get(DOC_TYPE, savedObjectId); if (error) { throw error; @@ -94,7 +94,7 @@ export class SavedObjectIndexStore implements SavedObjectStore { return { ...(attributes as SavedObjectAttributes), references, - id, + savedObjectId, type, } as Document; } diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index f9c63f54d6713..1655a571721f5 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -7,10 +7,12 @@ import { AppMountParameters, CoreSetup, CoreStart } from 'kibana/public'; import { DataPublicPluginSetup, DataPublicPluginStart } from 'src/plugins/data/public'; import { EmbeddableSetup, EmbeddableStart } from 'src/plugins/embeddable/public'; +import { DashboardStart } from 'src/plugins/dashboard/public'; import { ExpressionsSetup, ExpressionsStart } from 'src/plugins/expressions/public'; import { VisualizationsSetup } from 'src/plugins/visualizations/public'; import { NavigationPublicPluginStart } from 'src/plugins/navigation/public'; import { UrlForwardingSetup } from 'src/plugins/url_forwarding/public'; +import { GlobalSearchPluginSetup } from '../../global_search/public'; import { ChartsPluginSetup } from '../../../../src/plugins/charts/public'; import { EditorFrameService } from './editor_frame_service'; import { @@ -31,8 +33,10 @@ import { UiActionsStart } from '../../../../src/plugins/ui_actions/public'; import { NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../common'; import { EditorFrameStart } from './types'; import { getLensAliasConfig } from './vis_type_alias'; +import { getSearchProvider } from './search_provider'; import './index.scss'; +import { getLensAttributeService, LensAttributeService } from './lens_attribute_service'; export interface LensPluginSetupDependencies { urlForwarding: UrlForwardingSetup; @@ -41,6 +45,7 @@ export interface LensPluginSetupDependencies { embeddable?: EmbeddableSetup; visualizations: VisualizationsSetup; charts: ChartsPluginSetup; + globalSearch?: GlobalSearchPluginSetup; } export interface LensPluginStartDependencies { @@ -48,13 +53,14 @@ export interface LensPluginStartDependencies { expressions: ExpressionsStart; navigation: NavigationPublicPluginStart; uiActions: UiActionsStart; - embeddable: EmbeddableStart; + dashboard: DashboardStart; + embeddable?: EmbeddableStart; } - export class LensPlugin { private datatableVisualization: DatatableVisualization; private editorFrameService: EditorFrameService; private createEditorFrame: EditorFrameStart['createInstance'] | null = null; + private attributeService: LensAttributeService | null = null; private indexpatternDatasource: IndexPatternDatasource; private xyVisualization: XyVisualization; private metricVisualization: MetricVisualization; @@ -78,13 +84,18 @@ export class LensPlugin { embeddable, visualizations, charts, + globalSearch, }: LensPluginSetupDependencies ) { - const editorFrameSetupInterface = this.editorFrameService.setup(core, { - data, - embeddable, - expressions, - }); + const editorFrameSetupInterface = this.editorFrameService.setup( + core, + { + data, + embeddable, + expressions, + }, + () => this.attributeService! + ); const dependencies: IndexPatternDatasourceSetupPlugins & XyVisualizationPluginSetupPlugins & DatatableVisualizationPluginSetupPlugins & @@ -106,20 +117,44 @@ export class LensPlugin { visualizations.registerAlias(getLensAliasConfig()); + const getByValueFeatureFlag = async () => { + const [, deps] = await core.getStartServices(); + return deps.dashboard.dashboardFeatureFlagConfig; + }; + core.application.register({ id: 'lens', title: NOT_INTERNATIONALIZED_PRODUCT_NAME, navLinkStatus: AppNavLinkStatus.hidden, mount: async (params: AppMountParameters) => { const { mountApp } = await import('./app_plugin/mounter'); - return mountApp(core, params, this.createEditorFrame!); + return mountApp(core, params, { + createEditorFrame: this.createEditorFrame!, + attributeService: this.attributeService!, + getByValueFeatureFlag, + }); }, }); + if (globalSearch) { + globalSearch.registerResultProvider( + getSearchProvider( + core.getStartServices().then( + ([ + { + application: { capabilities }, + }, + ]) => capabilities + ) + ) + ); + } + urlForwarding.forwardApp('lens', 'lens'); } start(core: CoreStart, startDependencies: LensPluginStartDependencies) { + this.attributeService = getLensAttributeService(core, startDependencies); this.createEditorFrame = this.editorFrameService.start(core, startDependencies).createInstance; } diff --git a/x-pack/plugins/lens/public/search_provider.ts b/x-pack/plugins/lens/public/search_provider.ts new file mode 100644 index 0000000000000..c19e7970b45ae --- /dev/null +++ b/x-pack/plugins/lens/public/search_provider.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import levenshtein from 'js-levenshtein'; +import { ApplicationStart } from 'kibana/public'; +import { from } from 'rxjs'; +import { i18n } from '@kbn/i18n'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/public'; +import { GlobalSearchResultProvider } from '../../global_search/public'; +import { getFullPath } from '../common'; + +/** + * Global search provider adding a Lens entry. + * This is necessary because Lens does not show up in the nav bar and is filtered out by the + * default app provider. + * + * It is inlining the same search term matching logic as the application search provider. + * + * TODO: This is a workaround and can be removed once there is a generic way to register sub features + * of apps. In this case, Lens should be considered a feature of Visualize. + */ +export const getSearchProvider: ( + uiCapabilities: Promise +) => GlobalSearchResultProvider = (uiCapabilities) => ({ + id: 'lens', + find: (term) => { + return from( + uiCapabilities.then(({ navLinks: { visualize: visualizeNavLink } }) => { + if (!visualizeNavLink) { + return []; + } + const title = i18n.translate('xpack.lens.searchTitle', { + defaultMessage: 'Lens: create visualizations', + description: 'Lens is a product name and should not be translated', + }); + const searchableTitle = title.toLowerCase(); + + term = term.toLowerCase(); + let score = 0; + + // shortcuts to avoid calculating the distance when there is an exact match somewhere. + if (searchableTitle === term) { + score = 100; + } else if (searchableTitle.startsWith(term)) { + score = 90; + } else if (searchableTitle.includes(term)) { + score = 75; + } else { + const length = Math.max(term.length, searchableTitle.length); + const distance = levenshtein(term, searchableTitle); + + // maximum lev distance is length, we compute the match ratio (lower distance is better) + const ratio = Math.floor((1 - distance / length) * 100); + if (ratio >= 60) { + score = ratio; + } + } + if (score === 0) return []; + return [ + { + id: 'lens', + title, + type: 'application', + icon: 'logoKibana', + meta: { + categoryId: DEFAULT_APP_CATEGORIES.kibana.id, + categoryLabel: DEFAULT_APP_CATEGORIES.kibana.label, + }, + score, + url: getFullPath(), + }, + ]; + }) + ); + }, +}); diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx index c7781c2e1d50c..ee22ee51301df 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx @@ -17,7 +17,6 @@ import { EuiFormRow, EuiText, htmlIdGenerator, - EuiForm, EuiColorPicker, EuiColorPickerProps, EuiToolTip, @@ -366,7 +365,7 @@ export function DimensionEditor(props: VisualizationDimensionEditorProps) 'auto'; return ( - + <> ) }} /> - + ); } diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx index 9379c8a612eb2..24bf78dba2121 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx @@ -24,6 +24,7 @@ import { ExpressionFunctionDefinition, ExpressionRenderDefinition, ExpressionValueSearchContext, + KibanaDatatable, } from 'src/plugins/expressions/public'; import { IconType } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -251,6 +252,12 @@ export function XYChart({ ({ id }) => id === filteredLayers[0].xAccessor ); const xAxisFormatter = formatFactory(xAxisColumn && xAxisColumn.formatHint); + const layersAlreadyFormatted: Record = {}; + // This is a safe formatter for the xAccessor that abstracts the knowledge of already formatted layers + const safeXAccessorLabelRenderer = (value: unknown): string => + xAxisColumn && layersAlreadyFormatted[xAxisColumn.id] + ? (value as string) + : xAxisFormatter.convert(value); const chartHasMoreThanOneSeries = filteredLayers.length > 1 || @@ -364,7 +371,7 @@ export function XYChart({ theme={chartTheme} baseTheme={chartBaseTheme} tooltip={{ - headerFormatter: (d) => xAxisFormatter.convert(d.value), + headerFormatter: (d) => safeXAccessorLabelRenderer(d.value), }} rotation={shouldRotate ? 90 : 0} xDomain={xDomain} @@ -409,9 +416,15 @@ export function XYChart({ const points = [ { - row: table.rows.findIndex( - (row) => layer.xAccessor && row[layer.xAccessor] === xyGeometry.x - ), + row: table.rows.findIndex((row) => { + if (layer.xAccessor) { + if (layersAlreadyFormatted[layer.xAccessor]) { + // stringify the value to compare with the chart value + return xAxisFormatter.convert(row[layer.xAccessor]) === xyGeometry.x; + } + return row[layer.xAccessor] === xyGeometry.x; + } + }), column: table.columns.findIndex((col) => col.id === layer.xAccessor), value: xyGeometry.x, }, @@ -455,7 +468,7 @@ export function XYChart({ strokeWidth: 2, }} hide={filteredLayers[0].hide || !filteredLayers[0].xAccessor} - tickFormat={(d) => xAxisFormatter.convert(d)} + tickFormat={(d) => safeXAccessorLabelRenderer(d)} style={{ tickLabel: { visible: tickLabelsVisibilitySettings?.x, @@ -504,9 +517,43 @@ export function XYChart({ const table = data.tables[layerId]; + const isPrimitive = (value: unknown): boolean => + value != null && typeof value !== 'object'; + + // what if row values are not primitive? That is the case of, for instance, Ranges + // remaps them to their serialized version with the formatHint metadata + // In order to do it we need to make a copy of the table as the raw one is required for more features (filters, etc...) later on + const tableConverted: KibanaDatatable = { + ...table, + rows: table.rows.map((row) => { + const newRow = { ...row }; + for (const column of table.columns) { + const record = newRow[column.id]; + if (record && !isPrimitive(record)) { + newRow[column.id] = formatFactory(column.formatHint).convert(record); + } + } + return newRow; + }), + }; + + // save the id of the layer with the custom table + table.columns.reduce>( + (alreadyFormatted: Record, { id }) => { + if (alreadyFormatted[id]) { + return alreadyFormatted; + } + alreadyFormatted[id] = table.rows.some( + (row, i) => row[id] !== tableConverted.rows[i][id] + ); + return alreadyFormatted; + }, + layersAlreadyFormatted + ); + // For date histogram chart type, we're getting the rows that represent intervals without data. // To not display them in the legend, they need to be filtered out. - const rows = table.rows.filter( + const rows = tableConverted.rows.filter( (row) => !(xAccessor && typeof row[xAccessor] === 'undefined') && !( @@ -559,19 +606,28 @@ export function XYChart({ // * Key - Y name // * Formatted value - Y name if (accessors.length > 1) { - return d.seriesKeys + const result = d.seriesKeys .map((key: string | number, i) => { - if (i === 0 && splitHint) { + if ( + i === 0 && + splitHint && + splitAccessor && + !layersAlreadyFormatted[splitAccessor] + ) { return formatFactory(splitHint).convert(key); } return splitAccessor && i === 0 ? key : columnToLabelMap[key] ?? ''; }) .join(' - '); + return result; } // For formatted split series, format the key // This handles splitting by dates, for example if (splitHint) { + if (splitAccessor && layersAlreadyFormatted[splitAccessor]) { + return d.seriesKeys[0]; + } return formatFactory(splitHint).convert(d.seriesKeys[0]); } // This handles both split and single-y cases: diff --git a/x-pack/plugins/lens/server/routes/telemetry.ts b/x-pack/plugins/lens/server/routes/telemetry.ts index 7925416ff5df2..06a7091104871 100644 --- a/x-pack/plugins/lens/server/routes/telemetry.ts +++ b/x-pack/plugins/lens/server/routes/telemetry.ts @@ -15,7 +15,7 @@ export async function initLensUsageRoute(setup: CoreSetup) { const router = setup.http.createRouter(); router.post( { - path: `${BASE_API_URL}/telemetry`, + path: `${BASE_API_URL}/stats`, validate: { body: schema.object({ events: schema.mapOf(schema.string(), schema.mapOf(schema.string(), schema.number())), diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index d72d04d2a1843..be891b6e59608 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -37,6 +37,7 @@ export const FONTS_API_PATH = `${GIS_API_PATH}/fonts`; export const API_ROOT_PATH = `/${GIS_API_PATH}`; export const MVT_GETTILE_API_PATH = 'mvt/getTile'; +export const MVT_GETGRIDTILE_API_PATH = 'mvt/getGridTile'; export const MVT_SOURCE_LAYER_NAME = 'source_layer'; export const KBN_TOO_MANY_FEATURES_PROPERTY = '__kbn_too_many_features__'; export const KBN_TOO_MANY_FEATURES_IMAGE_ID = '__kbn_too_many_features_image_id__'; @@ -165,8 +166,13 @@ export enum GRID_RESOLUTION { COARSE = 'COARSE', FINE = 'FINE', MOST_FINE = 'MOST_FINE', + SUPER_FINE = 'SUPER_FINE', } +export const SUPER_FINE_ZOOM_DELTA = 7; // (2 ^ SUPER_FINE_ZOOM_DELTA) ^ 2 = number of cells in a given tile +export const GEOTILE_GRID_AGG_NAME = 'gridSplit'; +export const GEOCENTROID_AGG_NAME = 'gridCentroid'; + export const TOP_TERM_PERCENTAGE_SUFFIX = '__percentage'; export const COUNT_PROP_LABEL = i18n.translate('xpack.maps.aggs.defaultCountLabel', { @@ -230,8 +236,6 @@ export enum SCALING_TYPES { MVT = 'MVT', } -export const RGBA_0000 = 'rgba(0,0,0,0)'; - export enum MVT_FIELD_TYPE { STRING = 'String', NUMBER = 'Number', diff --git a/x-pack/plugins/maps/common/elasticsearch_util/convert_to_geojson.d.ts b/x-pack/plugins/maps/common/elasticsearch_util/convert_to_geojson.d.ts new file mode 100644 index 0000000000000..b1c1b181d8130 --- /dev/null +++ b/x-pack/plugins/maps/common/elasticsearch_util/convert_to_geojson.d.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Feature } from 'geojson'; +import { RENDER_AS } from '../constants'; + +export function convertCompositeRespToGeoJson(esResponse: any, renderAs: RENDER_AS): Feature[]; +export function convertRegularRespToGeoJson(esResponse: any, renderAs: RENDER_AS): Feature[]; diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/convert_to_geojson.js b/x-pack/plugins/maps/common/elasticsearch_util/convert_to_geojson.js similarity index 79% rename from x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/convert_to_geojson.js rename to x-pack/plugins/maps/common/elasticsearch_util/convert_to_geojson.js index 35dbebdfd3c8a..a8f32bb4e7f5d 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/convert_to_geojson.js +++ b/x-pack/plugins/maps/common/elasticsearch_util/convert_to_geojson.js @@ -5,12 +5,12 @@ */ import _ from 'lodash'; -import { RENDER_AS } from '../../../../common/constants'; -import { getTileBoundingBox } from './geo_tile_utils'; -import { extractPropertiesFromBucket } from '../../util/es_agg_utils'; -import { clamp } from '../../../../common/elasticsearch_geo_utils'; +import { RENDER_AS, GEOTILE_GRID_AGG_NAME, GEOCENTROID_AGG_NAME } from '../constants'; +import { getTileBoundingBox } from '../geo_tile_utils'; +import { extractPropertiesFromBucket } from './es_agg_utils'; +import { clamp } from './elasticsearch_geo_utils'; -const GRID_BUCKET_KEYS_TO_IGNORE = ['key', 'gridCentroid']; +const GRID_BUCKET_KEYS_TO_IGNORE = ['key', GEOCENTROID_AGG_NAME]; export function convertCompositeRespToGeoJson(esResponse, renderAs) { return convertToGeoJson( @@ -20,7 +20,7 @@ export function convertCompositeRespToGeoJson(esResponse, renderAs) { return _.get(esResponse, 'aggregations.compositeSplit.buckets', []); }, (gridBucket) => { - return gridBucket.key.gridSplit; + return gridBucket.key[GEOTILE_GRID_AGG_NAME]; } ); } @@ -30,7 +30,7 @@ export function convertRegularRespToGeoJson(esResponse, renderAs) { esResponse, renderAs, (esResponse) => { - return _.get(esResponse, 'aggregations.gridSplit.buckets', []); + return _.get(esResponse, `aggregations.${GEOTILE_GRID_AGG_NAME}.buckets`, []); }, (gridBucket) => { return gridBucket.key; @@ -49,7 +49,7 @@ function convertToGeoJson(esResponse, renderAs, pluckGridBuckets, pluckGridKey) type: 'Feature', geometry: rowToGeometry({ gridKey, - gridCentroid: gridBucket.gridCentroid, + [GEOCENTROID_AGG_NAME]: gridBucket[GEOCENTROID_AGG_NAME], renderAs, }), id: gridKey, diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/convert_to_geojson.test.ts b/x-pack/plugins/maps/common/elasticsearch_util/convert_to_geojson.test.ts similarity index 97% rename from x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/convert_to_geojson.test.ts rename to x-pack/plugins/maps/common/elasticsearch_util/convert_to_geojson.test.ts index 523cc86915010..ee40a1f2fc751 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/convert_to_geojson.test.ts +++ b/x-pack/plugins/maps/common/elasticsearch_util/convert_to_geojson.test.ts @@ -4,11 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('../../../kibana_services', () => {}); - // @ts-ignore import { convertCompositeRespToGeoJson, convertRegularRespToGeoJson } from './convert_to_geojson'; -import { RENDER_AS } from '../../../../common/constants'; +import { RENDER_AS } from '../constants'; describe('convertCompositeRespToGeoJson', () => { const esResponse = { diff --git a/x-pack/plugins/maps/common/elasticsearch_geo_utils.d.ts b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.d.ts similarity index 89% rename from x-pack/plugins/maps/common/elasticsearch_geo_utils.d.ts rename to x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.d.ts index e57efca94d95e..cff8ba119e1de 100644 --- a/x-pack/plugins/maps/common/elasticsearch_geo_utils.d.ts +++ b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.d.ts @@ -5,8 +5,8 @@ */ import { FeatureCollection, GeoJsonProperties } from 'geojson'; -import { MapExtent } from './descriptor_types'; -import { ES_GEO_FIELD_TYPE } from './constants'; +import { MapExtent } from '../descriptor_types'; +import { ES_GEO_FIELD_TYPE } from '../constants'; export function scaleBounds(bounds: MapExtent, scaleFactor: number): MapExtent; diff --git a/x-pack/plugins/maps/common/elasticsearch_geo_utils.js b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.js similarity index 98% rename from x-pack/plugins/maps/common/elasticsearch_geo_utils.js rename to x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.js index f2bf83ae18bb0..be214e3b01e67 100644 --- a/x-pack/plugins/maps/common/elasticsearch_geo_utils.js +++ b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.js @@ -15,9 +15,9 @@ import { POLYGON_COORDINATES_EXTERIOR_INDEX, LON_INDEX, LAT_INDEX, -} from '../common/constants'; -import { getEsSpatialRelationLabel } from './i18n_getters'; -import { FILTERS } from '../../../../src/plugins/data/common'; +} from '../constants'; +import { getEsSpatialRelationLabel } from '../i18n_getters'; +import { FILTERS } from '../../../../../src/plugins/data/common'; import turfCircle from '@turf/circle'; const SPATIAL_FILTER_TYPE = FILTERS.SPATIAL_FILTER; diff --git a/x-pack/plugins/maps/common/elasticsearch_geo_utils.test.js b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.test.js similarity index 100% rename from x-pack/plugins/maps/common/elasticsearch_geo_utils.test.js rename to x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.test.js diff --git a/x-pack/plugins/maps/public/classes/util/es_agg_utils.test.ts b/x-pack/plugins/maps/common/elasticsearch_util/es_agg_utils.test.ts similarity index 100% rename from x-pack/plugins/maps/public/classes/util/es_agg_utils.test.ts rename to x-pack/plugins/maps/common/elasticsearch_util/es_agg_utils.test.ts diff --git a/x-pack/plugins/maps/public/classes/util/es_agg_utils.ts b/x-pack/plugins/maps/common/elasticsearch_util/es_agg_utils.ts similarity index 92% rename from x-pack/plugins/maps/public/classes/util/es_agg_utils.ts rename to x-pack/plugins/maps/common/elasticsearch_util/es_agg_utils.ts index 329a2a6fc64fb..7828c3cc6410b 100644 --- a/x-pack/plugins/maps/public/classes/util/es_agg_utils.ts +++ b/x-pack/plugins/maps/common/elasticsearch_util/es_agg_utils.ts @@ -5,8 +5,8 @@ */ import { i18n } from '@kbn/i18n'; import _ from 'lodash'; -import { IndexPattern, IFieldType } from '../../../../../../src/plugins/data/public'; -import { TOP_TERM_PERCENTAGE_SUFFIX } from '../../../common/constants'; +import { IndexPattern, IFieldType } from '../../../../../src/plugins/data/common'; +import { TOP_TERM_PERCENTAGE_SUFFIX } from '../constants'; export function getField(indexPattern: IndexPattern, fieldName: string) { const field = indexPattern.fields.getByName(fieldName); diff --git a/x-pack/plugins/maps/common/elasticsearch_util/index.ts b/x-pack/plugins/maps/common/elasticsearch_util/index.ts new file mode 100644 index 0000000000000..ffb4a542374fa --- /dev/null +++ b/x-pack/plugins/maps/common/elasticsearch_util/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; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './es_agg_utils'; +export * from './convert_to_geojson'; +export * from './elasticsearch_geo_utils'; diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/geo_tile_utils.test.js b/x-pack/plugins/maps/common/geo_tile_utils.test.js similarity index 96% rename from x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/geo_tile_utils.test.js rename to x-pack/plugins/maps/common/geo_tile_utils.test.js index 88a6ce048a178..ae2623e168766 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/geo_tile_utils.test.js +++ b/x-pack/plugins/maps/common/geo_tile_utils.test.js @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('../../../kibana_services', () => {}); - import { parseTileKey, getTileBoundingBox, expandToTileBoundaries } from './geo_tile_utils'; it('Should parse tile key', () => { diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/geo_tile_utils.js b/x-pack/plugins/maps/common/geo_tile_utils.ts similarity index 65% rename from x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/geo_tile_utils.js rename to x-pack/plugins/maps/common/geo_tile_utils.ts index 89b24522e4275..c6e35224458c5 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/geo_tile_utils.js +++ b/x-pack/plugins/maps/common/geo_tile_utils.ts @@ -5,18 +5,32 @@ */ import _ from 'lodash'; -import { DECIMAL_DEGREES_PRECISION } from '../../../../common/constants'; -import { clampToLatBounds } from '../../../../common/elasticsearch_geo_utils'; +import { DECIMAL_DEGREES_PRECISION } from './constants'; +import { clampToLatBounds } from './elasticsearch_util'; +import { MapExtent } from './descriptor_types'; const ZOOM_TILE_KEY_INDEX = 0; const X_TILE_KEY_INDEX = 1; const Y_TILE_KEY_INDEX = 2; -function getTileCount(zoom) { +function getTileCount(zoom: number): number { return Math.pow(2, zoom); } -export function parseTileKey(tileKey) { +export interface ESBounds { + top_left: { + lon: number; + lat: number; + }; + bottom_right: { + lon: number; + lat: number; + }; +} + +export function parseTileKey( + tileKey: string +): { x: number; y: number; zoom: number; tileCount: number } { const tileKeyParts = tileKey.split('/'); if (tileKeyParts.length !== 3) { @@ -42,7 +56,7 @@ export function parseTileKey(tileKey) { return { x, y, zoom, tileCount }; } -function sinh(x) { +function sinh(x: number): number { return (Math.exp(x) - Math.exp(-x)) / 2; } @@ -55,24 +69,52 @@ function sinh(x) { // We add one extra decimal level of precision because, at high zoom // levels rounding exactly can cause the boxes to render as uneven sizes // (some will be slightly larger and some slightly smaller) -function precisionRounding(v, minPrecision, binSize) { +function precisionRounding(v: number, minPrecision: number, binSize: number): number { let precision = Math.ceil(Math.abs(Math.log10(binSize))) + 1; precision = Math.max(precision, minPrecision); return _.round(v, precision); } -function tileToLatitude(y, tileCount) { +export function tile2long(x: number, z: number): number { + const tileCount = getTileCount(z); + return tileToLongitude(x, tileCount); +} + +export function tile2lat(y: number, z: number): number { + const tileCount = getTileCount(z); + return tileToLatitude(y, tileCount); +} + +export function tileToESBbox(x: number, y: number, z: number): ESBounds { + const wLon = tile2long(x, z); + const sLat = tile2lat(y + 1, z); + const eLon = tile2long(x + 1, z); + const nLat = tile2lat(y, z); + + return { + top_left: { + lon: wLon, + lat: nLat, + }, + bottom_right: { + lon: eLon, + lat: sLat, + }, + }; +} + +export function tileToLatitude(y: number, tileCount: number) { const radians = Math.atan(sinh(Math.PI - (2 * Math.PI * y) / tileCount)); const lat = (180 / Math.PI) * radians; return precisionRounding(lat, DECIMAL_DEGREES_PRECISION, 180 / tileCount); } -function tileToLongitude(x, tileCount) { +export function tileToLongitude(x: number, tileCount: number) { const lon = (x / tileCount) * 360 - 180; return precisionRounding(lon, DECIMAL_DEGREES_PRECISION, 360 / tileCount); } -export function getTileBoundingBox(tileKey) { +export function getTileBoundingBox(tileKey: string) { const { x, y, tileCount } = parseTileKey(tileKey); return { @@ -83,22 +125,22 @@ export function getTileBoundingBox(tileKey) { }; } -function sec(value) { +function sec(value: number): number { return 1 / Math.cos(value); } -function latitudeToTile(lat, tileCount) { +function latitudeToTile(lat: number, tileCount: number) { const radians = (clampToLatBounds(lat) * Math.PI) / 180; const y = ((1 - Math.log(Math.tan(radians) + sec(radians)) / Math.PI) / 2) * tileCount; return Math.floor(y); } -function longitudeToTile(lon, tileCount) { +function longitudeToTile(lon: number, tileCount: number) { const x = ((lon + 180) / 360) * tileCount; return Math.floor(x); } -export function expandToTileBoundaries(extent, zoom) { +export function expandToTileBoundaries(extent: MapExtent, zoom: number): MapExtent { const tileCount = getTileCount(zoom); const upperLeftX = longitudeToTile(extent.minLon, tileCount); diff --git a/x-pack/plugins/maps/public/actions/data_request_actions.ts b/x-pack/plugins/maps/public/actions/data_request_actions.ts index 2876f3d668a69..14d8196900506 100644 --- a/x-pack/plugins/maps/public/actions/data_request_actions.ts +++ b/x-pack/plugins/maps/public/actions/data_request_actions.ts @@ -40,7 +40,7 @@ import { ILayer } from '../classes/layers/layer'; import { IVectorLayer } from '../classes/layers/vector_layer/vector_layer'; import { DataMeta, MapExtent, MapFilters } from '../../common/descriptor_types'; import { DataRequestAbortError } from '../classes/util/data_request'; -import { scaleBounds, turfBboxToBounds } from '../../common/elasticsearch_geo_utils'; +import { scaleBounds, turfBboxToBounds } from '../../common/elasticsearch_util'; import { IVectorStyle } from '../classes/styles/vector/vector_style'; const FIT_TO_BOUNDS_SCALE_FACTOR = 0.1; diff --git a/x-pack/plugins/maps/public/actions/map_actions.ts b/x-pack/plugins/maps/public/actions/map_actions.ts index b00594cb7fb23..09491e5c3a7b3 100644 --- a/x-pack/plugins/maps/public/actions/map_actions.ts +++ b/x-pack/plugins/maps/public/actions/map_actions.ts @@ -54,7 +54,7 @@ import { MapRefreshConfig, } from '../../common/descriptor_types'; import { INITIAL_LOCATION } from '../../common/constants'; -import { scaleBounds } from '../../common/elasticsearch_geo_utils'; +import { scaleBounds } from '../../common/elasticsearch_util'; export function setMapInitError(errorMessage: string) { return { diff --git a/x-pack/plugins/maps/public/classes/fields/es_agg_field.ts b/x-pack/plugins/maps/public/classes/fields/es_agg_field.ts index 7b184819b839b..8cff98205186f 100644 --- a/x-pack/plugins/maps/public/classes/fields/es_agg_field.ts +++ b/x-pack/plugins/maps/public/classes/fields/es_agg_field.ts @@ -12,7 +12,7 @@ import { IVectorSource } from '../sources/vector_source'; import { ESDocField } from './es_doc_field'; import { AGG_TYPE, FIELD_ORIGIN } from '../../../common/constants'; import { isMetricCountable } from '../util/is_metric_countable'; -import { getField, addFieldToDSL } from '../util/es_agg_utils'; +import { getField, addFieldToDSL } from '../../../common/elasticsearch_util'; import { TopTermPercentageField } from './top_term_percentage_field'; import { ITooltipProperty, TooltipProperty } from '../tooltips/tooltip_property'; import { ESAggTooltipProperty } from '../tooltips/es_agg_tooltip_property'; @@ -30,6 +30,7 @@ export class ESAggField implements IESAggField { private readonly _label?: string; private readonly _aggType: AGG_TYPE; private readonly _esDocField?: IField | undefined; + private readonly _canReadFromGeoJson: boolean; constructor({ label, @@ -37,18 +38,21 @@ export class ESAggField implements IESAggField { aggType, esDocField, origin, + canReadFromGeoJson = true, }: { label?: string; source: IESAggSource; aggType: AGG_TYPE; esDocField?: IField; origin: FIELD_ORIGIN; + canReadFromGeoJson?: boolean; }) { this._source = source; this._origin = origin; this._label = label; this._aggType = aggType; this._esDocField = esDocField; + this._canReadFromGeoJson = canReadFromGeoJson; } getSource(): IVectorSource { @@ -132,18 +136,19 @@ export class ESAggField implements IESAggField { } supportsAutoDomain(): boolean { - return true; + return this._canReadFromGeoJson ? true : this.supportsFieldMeta(); } canReadFromGeoJson(): boolean { - return true; + return this._canReadFromGeoJson; } } export function esAggFieldsFactory( aggDescriptor: AggDescriptor, source: IESAggSource, - origin: FIELD_ORIGIN + origin: FIELD_ORIGIN, + canReadFromGeoJson: boolean = true ): IESAggField[] { const aggField = new ESAggField({ label: aggDescriptor.label, @@ -153,12 +158,13 @@ export function esAggFieldsFactory( aggType: aggDescriptor.type, source, origin, + canReadFromGeoJson, }); const aggFields: IESAggField[] = [aggField]; if (aggDescriptor.field && aggDescriptor.type === AGG_TYPE.TERMS) { - aggFields.push(new TopTermPercentageField(aggField)); + aggFields.push(new TopTermPercentageField(aggField, canReadFromGeoJson)); } return aggFields; diff --git a/x-pack/plugins/maps/public/classes/fields/field.ts b/x-pack/plugins/maps/public/classes/fields/field.ts index 2c190d54f0265..658c2bba87847 100644 --- a/x-pack/plugins/maps/public/classes/fields/field.ts +++ b/x-pack/plugins/maps/public/classes/fields/field.ts @@ -21,12 +21,12 @@ export interface IField { getOrdinalFieldMetaRequest(): Promise; getCategoricalFieldMetaRequest(size: number): Promise; - // Determines whether Maps-app can automatically determine the domain of the field-values + // Whether Maps-app can automatically determine the domain of the field-values // if this is not the case (e.g. for .mvt tiled data), // then styling properties that require the domain to be known cannot use this property. supportsAutoDomain(): boolean; - // Determinse wheter Maps-app can automatically deterime the domain of the field-values + // Whether Maps-app can automatically determine the domain of the field-values // _without_ having to retrieve the data as GeoJson // e.g. for ES-sources, this would use the extended_stats API supportsFieldMeta(): boolean; diff --git a/x-pack/plugins/maps/public/classes/fields/top_term_percentage_field.ts b/x-pack/plugins/maps/public/classes/fields/top_term_percentage_field.ts index fc931b13619ef..50db04d08b2aa 100644 --- a/x-pack/plugins/maps/public/classes/fields/top_term_percentage_field.ts +++ b/x-pack/plugins/maps/public/classes/fields/top_term_percentage_field.ts @@ -6,16 +6,17 @@ import { IESAggField } from './es_agg_field'; import { IVectorSource } from '../sources/vector_source'; -// @ts-ignore import { ITooltipProperty, TooltipProperty } from '../tooltips/tooltip_property'; import { TOP_TERM_PERCENTAGE_SUFFIX } from '../../../common/constants'; import { FIELD_ORIGIN } from '../../../common/constants'; export class TopTermPercentageField implements IESAggField { private readonly _topTermAggField: IESAggField; + private readonly _canReadFromGeoJson: boolean; - constructor(topTermAggField: IESAggField) { + constructor(topTermAggField: IESAggField, canReadFromGeoJson: boolean = true) { this._topTermAggField = topTermAggField; + this._canReadFromGeoJson = canReadFromGeoJson; } getSource(): IVectorSource { @@ -61,7 +62,7 @@ export class TopTermPercentageField implements IESAggField { } supportsAutoDomain(): boolean { - return true; + return this._canReadFromGeoJson; } supportsFieldMeta(): boolean { @@ -81,6 +82,6 @@ export class TopTermPercentageField implements IESAggField { } canReadFromGeoJson(): boolean { - return true; + return this._canReadFromGeoJson; } } diff --git a/x-pack/plugins/maps/public/classes/layers/layer.tsx b/x-pack/plugins/maps/public/classes/layers/layer.tsx index 8026f48fe6093..cd720063c6703 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/layer.tsx @@ -423,7 +423,7 @@ export class AbstractLayer implements ILayer { renderSourceSettingsEditor({ onChange }: SourceEditorArgs) { const source = this.getSourceForEditing(); - return source.renderSourceSettingsEditor({ onChange }); + return source.renderSourceSettingsEditor({ onChange, currentLayerType: this._descriptor.type }); } getPrevRequestToken(dataId: string): symbol | undefined { diff --git a/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.ts b/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.ts index a9c886617d3af..be947d79f4e39 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.ts @@ -31,14 +31,25 @@ export interface IESAggSource extends IESSource { export class AbstractESAggSource extends AbstractESSource { private readonly _metricFields: IESAggField[]; + private readonly _canReadFromGeoJson: boolean; - constructor(descriptor: AbstractESAggSourceDescriptor, inspectorAdapters: Adapters) { + constructor( + descriptor: AbstractESAggSourceDescriptor, + inspectorAdapters: Adapters, + canReadFromGeoJson = true + ) { super(descriptor, inspectorAdapters); this._metricFields = []; + this._canReadFromGeoJson = canReadFromGeoJson; if (descriptor.metrics) { descriptor.metrics.forEach((aggDescriptor: AggDescriptor) => { this._metricFields.push( - ...esAggFieldsFactory(aggDescriptor, this, this.getOriginForField()) + ...esAggFieldsFactory( + aggDescriptor, + this, + this.getOriginForField(), + this._canReadFromGeoJson + ) ); }); } @@ -72,7 +83,12 @@ export class AbstractESAggSource extends AbstractESSource { const metrics = this._metricFields.filter((esAggField) => esAggField.isValid()); // Handle case where metrics is empty because older saved object state is empty array or there are no valid aggs. return metrics.length === 0 - ? esAggFieldsFactory({ type: AGG_TYPE.COUNT }, this, this.getOriginForField()) + ? esAggFieldsFactory( + { type: AGG_TYPE.COUNT }, + this, + this.getOriginForField(), + this._canReadFromGeoJson + ) : metrics; } diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/__snapshots__/resolution_editor.test.tsx.snap b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/__snapshots__/resolution_editor.test.tsx.snap new file mode 100644 index 0000000000000..ca9775594a9d7 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/__snapshots__/resolution_editor.test.tsx.snap @@ -0,0 +1,73 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`resolution editor should add super-fine option 1`] = ` + + + +`; + +exports[`resolution editor should omit super-fine option 1`] = ` + + + +`; diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/__snapshots__/update_source_editor.test.tsx.snap b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/__snapshots__/update_source_editor.test.tsx.snap new file mode 100644 index 0000000000000..dfce6b36396a7 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/__snapshots__/update_source_editor.test.tsx.snap @@ -0,0 +1,121 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`source editor geo_grid_source default vector layer config should allow super-fine option 1`] = ` + + + +
+ +
+
+ + +
+ + + +
+ +
+
+ + + +
+ +
+`; + +exports[`source editor geo_grid_source should put limitations based on heatmap-rendering selection should not allow super-fine option for heatmaps and should not allow multiple metrics 1`] = ` + + + +
+ +
+
+ + +
+ + + +
+ +
+
+ + + +
+ +
+`; diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.d.ts b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.d.ts index 2ce4353fca13c..ada76b8e4e674 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.d.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.d.ts @@ -5,11 +5,17 @@ */ import { AbstractESAggSource } from '../es_agg_source'; -import { ESGeoGridSourceDescriptor } from '../../../../common/descriptor_types'; +import { + ESGeoGridSourceDescriptor, + MapFilters, + MapQuery, + VectorSourceSyncMeta, +} from '../../../../common/descriptor_types'; import { GRID_RESOLUTION } from '../../../../common/constants'; import { IField } from '../../fields/field'; +import { ITiledSingleLayerVectorSource } from '../vector_source'; -export class ESGeoGridSource extends AbstractESAggSource { +export class ESGeoGridSource extends AbstractESAggSource implements ITiledSingleLayerVectorSource { static createDescriptor({ indexPatternId, geoField, @@ -19,8 +25,27 @@ export class ESGeoGridSource extends AbstractESAggSource { constructor(sourceDescriptor: ESGeoGridSourceDescriptor, inspectorAdapters: unknown); + readonly _descriptor: ESGeoGridSourceDescriptor; + getFieldNames(): string[]; getGridResolution(): GRID_RESOLUTION; getGeoGridPrecision(zoom: number): number; createField({ fieldName }: { fieldName: string }): IField; + + getLayerName(): string; + + getUrlTemplateWithMeta( + searchFilters: MapFilters & { + applyGlobalQuery: boolean; + fieldNames: string[]; + geogridPrecision?: number; + sourceQuery: MapQuery; + sourceMeta: VectorSourceSyncMeta; + } + ): Promise<{ + layerName: string; + urlTemplate: string; + minSourceZoom: number; + maxSourceZoom: number; + }>; } diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.js b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.js index aa167cb577672..89258f04612fd 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.js +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.js @@ -7,7 +7,11 @@ import React from 'react'; import uuid from 'uuid/v4'; -import { convertCompositeRespToGeoJson, convertRegularRespToGeoJson } from './convert_to_geojson'; +import { + convertCompositeRespToGeoJson, + convertRegularRespToGeoJson, + makeESBbox, +} from '../../../../common/elasticsearch_util'; import { UpdateSourceEditor } from './update_source_editor'; import { SOURCE_TYPES, @@ -15,13 +19,20 @@ import { RENDER_AS, GRID_RESOLUTION, VECTOR_SHAPE_TYPE, + MVT_SOURCE_LAYER_NAME, + GIS_API_PATH, + MVT_GETGRIDTILE_API_PATH, + GEOTILE_GRID_AGG_NAME, + GEOCENTROID_AGG_NAME, } from '../../../../common/constants'; import { i18n } from '@kbn/i18n'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { AbstractESAggSource, DEFAULT_METRIC } from '../es_agg_source'; import { DataRequestAbortError } from '../../util/data_request'; import { registerSource } from '../source_registry'; -import { makeESBbox } from '../../../../common/elasticsearch_geo_utils'; + +import rison from 'rison-node'; +import { getHttp } from '../../../kibana_services'; export const MAX_GEOTILE_LEVEL = 29; @@ -48,9 +59,14 @@ export class ESGeoGridSource extends AbstractESAggSource { }; } - renderSourceSettingsEditor({ onChange }) { + constructor(descriptor, inspectorAdapters) { + super(descriptor, inspectorAdapters, descriptor.resolution !== GRID_RESOLUTION.SUPER_FINE); + } + + renderSourceSettingsEditor({ onChange, currentLayerType }) { return ( { @@ -96,59 +99,67 @@ describe('ESGeoGridSource', () => { }; }; - describe('getGeoJsonWithMeta', () => { - let mockSearchSource: unknown; - beforeEach(async () => { - mockSearchSource = new MockSearchSource(); - const mockSearchService = { - searchSource: { - async create() { - return mockSearchSource as SearchSource; - }, - createEmpty() { - return mockSearchSource as SearchSource; - }, + let mockSearchSource: unknown; + beforeEach(async () => { + mockSearchSource = new MockSearchSource(); + const mockSearchService = { + searchSource: { + async create() { + return mockSearchSource as SearchSource; }, - }; + createEmpty() { + return mockSearchSource as SearchSource; + }, + }, + }; - // @ts-expect-error - getIndexPatternService.mockReturnValue(mockIndexPatternService); - // @ts-expect-error - getSearchService.mockReturnValue(mockSearchService); + // @ts-expect-error + getIndexPatternService.mockReturnValue(mockIndexPatternService); + // @ts-expect-error + getSearchService.mockReturnValue(mockSearchService); + // @ts-expect-error + getHttp.mockReturnValue({ + basePath: { + prepend(path: string) { + return `rootdir${path};`; + }, + }, }); + }); - const extent: MapExtent = { - minLon: -160, - minLat: -80, - maxLon: 160, - maxLat: 80, - }; + const extent: MapExtent = { + minLon: -160, + minLat: -80, + maxLon: 160, + maxLat: 80, + }; - const mapFilters: VectorSourceRequestMeta = { - geogridPrecision: 4, - filters: [], - timeFilters: { - from: 'now', - to: '15m', - mode: 'relative', - }, - extent, - applyGlobalQuery: true, - fieldNames: [], - buffer: extent, - sourceQuery: { - query: '', - language: 'KQL', - queryLastTriggeredAt: '2019-04-25T20:53:22.331Z', - }, - sourceMeta: null, - zoom: 0, - }; + const vectorSourceRequestMeta: VectorSourceRequestMeta = { + geogridPrecision: 4, + filters: [], + timeFilters: { + from: 'now', + to: '15m', + mode: 'relative', + }, + extent, + applyGlobalQuery: true, + fieldNames: [], + buffer: extent, + sourceQuery: { + query: '', + language: 'KQL', + queryLastTriggeredAt: '2019-04-25T20:53:22.331Z', + }, + sourceMeta: null, + zoom: 0, + }; + describe('getGeoJsonWithMeta', () => { it('Should configure the SearchSource correctly', async () => { const { data, meta } = await geogridSource.getGeoJsonWithMeta( 'foobarLayer', - mapFilters, + vectorSourceRequestMeta, () => {} ); @@ -215,5 +226,48 @@ describe('ESGeoGridSource', () => { it('should use heuristic to derive precision', () => { expect(geogridSource.getGeoGridPrecision(10)).toBe(12); }); + + it('Should not return valid precision for super-fine resolution', () => { + const superFineSource = new ESGeoGridSource( + { + id: 'foobar', + indexPatternId: 'fooIp', + geoField: geoFieldName, + metrics: [], + resolution: GRID_RESOLUTION.SUPER_FINE, + type: SOURCE_TYPES.ES_GEO_GRID, + requestType: RENDER_AS.HEATMAP, + }, + {} + ); + expect(superFineSource.getGeoGridPrecision(10)).toBe(NaN); + }); + }); + + describe('ITiledSingleLayerVectorSource', () => { + it('getLayerName', () => { + expect(geogridSource.getLayerName()).toBe('source_layer'); + }); + + it('getMinZoom', () => { + expect(geogridSource.getMinZoom()).toBe(0); + }); + + it('getMaxZoom', () => { + expect(geogridSource.getMaxZoom()).toBe(24); + }); + + it('getUrlTemplateWithMeta', async () => { + const urlTemplateWithMeta = await geogridSource.getUrlTemplateWithMeta( + vectorSourceRequestMeta + ); + + expect(urlTemplateWithMeta.layerName).toBe('source_layer'); + expect(urlTemplateWithMeta.minSourceZoom).toBe(0); + expect(urlTemplateWithMeta.maxSourceZoom).toBe(24); + expect(urlTemplateWithMeta.urlTemplate).toBe( + "rootdir/api/maps/mvt/getGridTile;?x={x}&y={y}&z={z}&geometryFieldName=bar&index=undefined&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:())),'1':('0':size,'1':0),'2':('0':filter,'1':!((geo_bounding_box:(bar:(bottom_right:!(180,-82.67628),top_left:!(-180,82.67628)))))),'3':('0':query),'4':('0':index,'1':(fields:())),'5':('0':query,'1':(language:KQL,query:'',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':aggs,'1':(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:bar))),geotile_grid:(bounds:!n,field:bar,precision:!n,shard_size:65535,size:65535))))))&requestType=heatmap&geoFieldType=geo_point" + ); + }); }); }); diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/resolution_editor.js b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/resolution_editor.js index 28c24f58a0efc..71133cb25280c 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/resolution_editor.js +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/resolution_editor.js @@ -9,7 +9,7 @@ import { GRID_RESOLUTION } from '../../../../common/constants'; import { EuiSelect, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -const OPTIONS = [ +const BASE_OPTIONS = [ { value: GRID_RESOLUTION.COARSE, text: i18n.translate('xpack.maps.source.esGrid.coarseDropdownOption', { @@ -30,7 +30,18 @@ const OPTIONS = [ }, ]; -export function ResolutionEditor({ resolution, onChange }) { +export function ResolutionEditor({ resolution, onChange, includeSuperFine }) { + const options = [...BASE_OPTIONS]; + + if (includeSuperFine) { + options.push({ + value: GRID_RESOLUTION.SUPER_FINE, + text: i18n.translate('xpack.maps.source.esGrid.superFineDropDownOption', { + defaultMessage: 'super fine (beta)', + }), + }); + } + return ( onChange(e.target.value)} compressed diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/resolution_editor.test.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/resolution_editor.test.tsx new file mode 100644 index 0000000000000..369203dbe16c0 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/resolution_editor.test.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +// @ts-expect-error +import { ResolutionEditor } from './resolution_editor'; +import { GRID_RESOLUTION } from '../../../../common/constants'; + +const defaultProps = { + resolution: GRID_RESOLUTION.COARSE, + onChange: () => {}, + includeSuperFine: false, +}; + +describe('resolution editor', () => { + test('should omit super-fine option', () => { + const component = shallow(); + expect(component).toMatchSnapshot(); + }); + test('should add super-fine option', () => { + const component = shallow(); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/update_source_editor.js b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/update_source_editor.js index ac7d809c40f61..7e885c291b952 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/update_source_editor.js +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/update_source_editor.js @@ -6,7 +6,7 @@ import React, { Fragment, Component } from 'react'; -import { RENDER_AS } from '../../../../common/constants'; +import { GRID_RESOLUTION, LAYER_TYPE } from '../../../../common/constants'; import { MetricsEditor } from '../../../components/metrics_editor'; import { getIndexPatternService } from '../../../kibana_services'; import { ResolutionEditor } from './resolution_editor'; @@ -62,8 +62,25 @@ export class UpdateSourceEditor extends Component { this.props.onChange({ propName: 'metrics', value: metrics }); }; - _onResolutionChange = (e) => { - this.props.onChange({ propName: 'resolution', value: e }); + _onResolutionChange = (resolution) => { + let newLayerType; + if ( + this.props.currentLayerType === LAYER_TYPE.VECTOR || + this.props.currentLayerType === LAYER_TYPE.TILED_VECTOR + ) { + newLayerType = + resolution === GRID_RESOLUTION.SUPER_FINE ? LAYER_TYPE.TILED_VECTOR : LAYER_TYPE.VECTOR; + } else if (this.props.currentLayerType === LAYER_TYPE.HEATMAP) { + if (resolution === GRID_RESOLUTION.SUPER_FINE) { + throw new Error('Heatmap does not support SUPER_FINE resolution'); + } else { + newLayerType = LAYER_TYPE.HEATMAP; + } + } else { + throw new Error('Unexpected layer-type'); + } + + this.props.onChange({ propName: 'resolution', value: resolution, newLayerType }); }; _onRequestTypeSelect = (requestType) => { @@ -72,13 +89,13 @@ export class UpdateSourceEditor extends Component { _renderMetricsPanel() { const metricsFilter = - this.props.renderAs === RENDER_AS.HEATMAP + this.props.currentLayerType === LAYER_TYPE.HEATMAP ? (metric) => { //these are countable metrics, where blending heatmap color blobs make sense return isMetricCountable(metric.value); } : null; - const allowMultipleMetrics = this.props.renderAs !== RENDER_AS.HEATMAP; + const allowMultipleMetrics = this.props.currentLayerType !== LAYER_TYPE.HEATMAP; return ( @@ -115,6 +132,7 @@ export class UpdateSourceEditor extends Component { diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/update_source_editor.test.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/update_source_editor.test.tsx new file mode 100644 index 0000000000000..ceb79230bc832 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/update_source_editor.test.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +// @ts-expect-error +import { UpdateSourceEditor } from './update_source_editor'; +import { GRID_RESOLUTION, LAYER_TYPE, RENDER_AS } from '../../../../common/constants'; + +const defaultProps = { + currentLayerType: LAYER_TYPE.VECTOR, + indexPatternId: 'foobar', + onChange: () => {}, + metrics: [], + renderAs: RENDER_AS.POINT, + resolution: GRID_RESOLUTION.COARSE, +}; + +describe('source editor geo_grid_source', () => { + describe('default vector layer config', () => { + test('should allow super-fine option', async () => { + const component = shallow(); + expect(component).toMatchSnapshot(); + }); + }); + + describe('should put limitations based on heatmap-rendering selection', () => { + test('should not allow super-fine option for heatmaps and should not allow multiple metrics', async () => { + const component = shallow( + + ); + expect(component).toMatchSnapshot(); + }); + }); +}); diff --git a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/convert_to_lines.js b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/convert_to_lines.js index 96a7f50cdf523..24ac6d31bc645 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/convert_to_lines.js +++ b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/convert_to_lines.js @@ -5,7 +5,7 @@ */ import _ from 'lodash'; -import { extractPropertiesFromBucket } from '../../util/es_agg_utils'; +import { extractPropertiesFromBucket } from '../../../../common/elasticsearch_util'; const LAT_INDEX = 0; const LON_INDEX = 1; diff --git a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js index 9ec54335d4e78..0360208ef8370 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js +++ b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js @@ -16,7 +16,7 @@ import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { convertToLines } from './convert_to_lines'; import { AbstractESAggSource, DEFAULT_METRIC } from '../es_agg_source'; import { registerSource } from '../source_registry'; -import { turfBboxToBounds } from '../../../../common/elasticsearch_geo_utils'; +import { turfBboxToBounds } from '../../../../common/elasticsearch_util'; import { DataRequestAbortError } from '../../util/data_request'; const MAX_GEOTILE_LEVEL = 29; diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.js b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.js index df83bd1cf5e60..edcafae54d54c 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.js +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.js @@ -10,7 +10,7 @@ import rison from 'rison-node'; import { AbstractESSource } from '../es_source'; import { getSearchService, getHttp } from '../../../kibana_services'; -import { hitsToGeoJson } from '../../../../common/elasticsearch_geo_utils'; +import { hitsToGeoJson, getField, addFieldToDSL } from '../../../../common/elasticsearch_util'; import { UpdateSourceEditor } from './update_source_editor'; import { SOURCE_TYPES, @@ -31,7 +31,7 @@ import uuid from 'uuid/v4'; import { DEFAULT_FILTER_BY_MAP_BOUNDS } from './constants'; import { ESDocField } from '../../fields/es_doc_field'; -import { getField, addFieldToDSL } from '../../util/es_agg_utils'; + import { registerSource } from '../source_registry'; export const sourceTitle = i18n.translate('xpack.maps.source.esSearchTitle', { diff --git a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js index d51ca46fd98ff..ab56ceeab4e77 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js +++ b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js @@ -11,14 +11,14 @@ import { getTimeFilter, getSearchService, } from '../../../kibana_services'; -import { createExtentFilter } from '../../../../common/elasticsearch_geo_utils'; +import { createExtentFilter } from '../../../../common/elasticsearch_util'; import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import uuid from 'uuid/v4'; import { copyPersistentState } from '../../../reducers/util'; import { DataRequestAbortError } from '../../util/data_request'; -import { expandToTileBoundaries } from '../es_geo_grid_source/geo_tile_utils'; +import { expandToTileBoundaries } from '../../../../common/geo_tile_utils'; import { search } from '../../../../../../../src/plugins/data/public'; export class AbstractESSource extends AbstractVectorSource { diff --git a/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.js b/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.js index b4ad256c1598a..359d22d2c44ce 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.js +++ b/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.js @@ -16,7 +16,11 @@ import { import { getJoinAggKey } from '../../../../common/get_agg_key'; import { ESDocField } from '../../fields/es_doc_field'; import { AbstractESAggSource } from '../es_agg_source'; -import { getField, addFieldToDSL, extractPropertiesFromBucket } from '../../util/es_agg_utils'; +import { + getField, + addFieldToDSL, + extractPropertiesFromBucket, +} from '../../../../common/elasticsearch_util'; const TERMS_AGG_NAME = 'join'; diff --git a/x-pack/plugins/maps/public/classes/sources/source.ts b/x-pack/plugins/maps/public/classes/sources/source.ts index 7e7a7bd8f049d..946381817b8fc 100644 --- a/x-pack/plugins/maps/public/classes/sources/source.ts +++ b/x-pack/plugins/maps/public/classes/sources/source.ts @@ -18,6 +18,7 @@ import { OnSourceChangeArgs } from '../../connected_components/layer_panel/view' export type SourceEditorArgs = { onChange: (...args: OnSourceChangeArgs[]) => void; + currentLayerType?: string; }; export type ImmutableSourceProperty = { @@ -50,7 +51,7 @@ export interface ISource { getImmutableProperties(): Promise; getAttributions(): Promise; isESSource(): boolean; - renderSourceSettingsEditor({ onChange }: SourceEditorArgs): ReactElement | null; + renderSourceSettingsEditor(sourceEditorArgs: SourceEditorArgs): ReactElement | null; supportsFitToBounds(): Promise; showJoinEditor(): boolean; getJoinsDisabledReason(): string | null; @@ -126,7 +127,7 @@ export class AbstractSource implements ISource { return []; } - renderSourceSettingsEditor({ onChange }: SourceEditorArgs): ReactElement | null { + renderSourceSettingsEditor(sourceEditorArgs: SourceEditorArgs): ReactElement | null { return null; } diff --git a/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx b/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx index a300225178526..c75698805225f 100644 --- a/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx +++ b/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx @@ -86,6 +86,7 @@ export class HeatmapStyle implements IStyle { } else if (resolution === GRID_RESOLUTION.MOST_FINE) { radius = 32; } else { + // SUPER_FINE or any other is not supported. const errorMessage = i18n.translate('xpack.maps.style.heatmap.resolutionStyleErrorMessage', { defaultMessage: `Resolution param not recognized: {resolution}`, values: { resolution }, diff --git a/x-pack/plugins/maps/public/classes/styles/vector/style_util.ts b/x-pack/plugins/maps/public/classes/styles/vector/style_util.ts index d190a62e6f300..49d6ccdeb9316 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/style_util.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/style_util.ts @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { MB_LOOKUP_FUNCTION, VECTOR_SHAPE_TYPE } from '../../../../common/constants'; +import { MB_LOOKUP_FUNCTION, VECTOR_SHAPE_TYPE, VECTOR_STYLES } from '../../../../common/constants'; import { Category } from '../../../../common/descriptor_types'; export function getOtherCategoryLabel() { @@ -14,8 +14,8 @@ export function getOtherCategoryLabel() { }); } -export function getComputedFieldName(styleName: string, fieldName: string) { - return `${getComputedFieldNamePrefix(fieldName)}__${styleName}`; +export function getComputedFieldName(styleName: VECTOR_STYLES, fieldName: string) { + return `${getComputedFieldNamePrefix(fieldName)}__${styleName as string}`; } export function getComputedFieldNamePrefix(fieldName: string) { diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js index 98267965fd30f..33a0f1c5bf088 100644 --- a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js +++ b/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js @@ -8,7 +8,7 @@ import React, { Component } from 'react'; import { i18n } from '@kbn/i18n'; import { URL_MAX_LENGTH } from '../../../../../../../src/core/public'; -import { createSpatialFilterWithGeometry } from '../../../../common/elasticsearch_geo_utils'; +import { createSpatialFilterWithGeometry } from '../../../../common/elasticsearch_util'; import { GEO_JSON_TYPE } from '../../../../common/constants'; import { GeometryFilterForm } from '../../../components/geometry_filter_form'; diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js b/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js index 49675ac6a3924..0356a8267c18a 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js +++ b/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js @@ -15,7 +15,7 @@ import { createSpatialFilterWithGeometry, getBoundingBoxGeometry, roundCoordinates, -} from '../../../../../common/elasticsearch_geo_utils'; +} from '../../../../../common/elasticsearch_util'; import { DrawTooltip } from './draw_tooltip'; const DRAW_RECTANGLE = 'draw_rectangle'; diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/view.js b/x-pack/plugins/maps/public/connected_components/map/mb/view.js index eede1edf40cc4..ddc48cfc9c329 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/view.js +++ b/x-pack/plugins/maps/public/connected_components/map/mb/view.js @@ -23,7 +23,7 @@ import sprites1 from '@elastic/maki/dist/sprite@1.png'; import sprites2 from '@elastic/maki/dist/sprite@2.png'; import { DrawControl } from './draw_control'; import { TooltipControl } from './tooltip_control'; -import { clampToLatBounds, clampToLonBounds } from '../../../../common/elasticsearch_geo_utils'; +import { clampToLatBounds, clampToLonBounds } from '../../../../common/elasticsearch_util'; import { getInitialView } from './get_initial_view'; import { getPreserveDrawingBuffer } from '../../../kibana_services'; diff --git a/x-pack/plugins/maps/public/routing/routes/maps_app/top_nav_config.tsx b/x-pack/plugins/maps/public/routing/routes/maps_app/top_nav_config.tsx index 47f41f2b76f3e..8a0eb8db4d7aa 100644 --- a/x-pack/plugins/maps/public/routing/routes/maps_app/top_nav_config.tsx +++ b/x-pack/plugins/maps/public/routing/routes/maps_app/top_nav_config.tsx @@ -67,17 +67,17 @@ export function getTopNavConfig({ savedMap.description = newDescription; savedMap.copyOnSave = newCopyOnSave; - let id; + let savedObjectId; try { savedMap.syncWithStore(); - id = await savedMap.save({ + savedObjectId = await savedMap.save({ confirmOverwrite: false, isTitleDuplicateConfirmed, onTitleDuplicate, }); // id not returned when save fails because of duplicate title check. // return and let user confirm duplicate title. - if (!id) { + if (!savedObjectId) { return {}; } } catch (err) { @@ -105,7 +105,7 @@ export function getTopNavConfig({ getCoreChrome().docTitle.change(savedMap.title); setBreadcrumbs(); - goToSpecifiedPath(`/map/${id}${window.location.hash}`); + goToSpecifiedPath(`/map/${savedObjectId}${window.location.hash}`); const newlyCreated = newCopyOnSave || isNewMap; if (newlyCreated && !returnToOrigin) { @@ -113,14 +113,14 @@ export function getTopNavConfig({ } else if (!!originatingApp && returnToOrigin) { if (newlyCreated && stateTransfer) { stateTransfer.navigateToWithEmbeddablePackage(originatingApp, { - state: { id, type: MAP_SAVED_OBJECT_TYPE }, + state: { input: { savedObjectId }, type: MAP_SAVED_OBJECT_TYPE }, }); } else { getNavigateToApp()(originatingApp); } } - return { id }; + return { id: savedObjectId }; } if (hasSaveAndReturnConfig) { diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.ts b/x-pack/plugins/maps/public/selectors/map_selectors.ts index 03e0f753812c9..db4371e9cd590 100644 --- a/x-pack/plugins/maps/public/selectors/map_selectors.ts +++ b/x-pack/plugins/maps/public/selectors/map_selectors.ts @@ -31,7 +31,7 @@ import { SPATIAL_FILTERS_LAYER_ID, } from '../../common/constants'; // @ts-ignore -import { extractFeaturesFromFilters } from '../../common/elasticsearch_geo_utils'; +import { extractFeaturesFromFilters } from '../../common/elasticsearch_util'; import { MapStoreState } from '../reducers/store'; import { DataRequestDescriptor, 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 8688bbe549f51..2af6413da039b 100644 --- a/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts +++ b/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts @@ -6,11 +6,12 @@ import _ from 'lodash'; import { + SavedObject, SavedObjectAttribute, SavedObjectAttributes, SavedObjectsClientContract, } from 'kibana/server'; -import { IFieldType, IIndexPattern } from 'src/plugins/data/public'; +import { IFieldType, IndexPatternAttributes } from 'src/plugins/data/public'; import { ES_GEO_FIELD_TYPE, LAYER_TYPE, @@ -64,7 +65,9 @@ function getUniqueLayerCounts(layerCountsList: ILayerTypeCount[], mapsCount: num }, {}); } -function getIndexPatternsWithGeoFieldCount(indexPatterns: IIndexPattern[]) { +function getIndexPatternsWithGeoFieldCount( + indexPatterns: Array> +) { const fieldLists = indexPatterns.map((indexPattern) => indexPattern.attributes && indexPattern.attributes.fields ? JSON.parse(indexPattern.attributes.fields) @@ -112,7 +115,7 @@ function getEMSLayerCount(layerLists: LayerDescriptor[][]): ILayerTypeCount[] { } function isFieldGeoShape( - indexPatterns: IIndexPattern[], + indexPatterns: Array>, indexPatternId: string, geoField: string | undefined ): boolean { @@ -120,9 +123,11 @@ function isFieldGeoShape( return false; } - const matchIndexPattern = indexPatterns.find((indexPattern: IIndexPattern) => { - return indexPattern.id === indexPatternId; - }); + const matchIndexPattern = indexPatterns.find( + (indexPattern: SavedObject) => { + return indexPattern.id === indexPatternId; + } + ); if (!matchIndexPattern) { return false; @@ -140,7 +145,10 @@ function isFieldGeoShape( return !!matchField && matchField.type === ES_GEO_FIELD_TYPE.GEO_SHAPE; } -function isGeoShapeAggLayer(indexPatterns: IIndexPattern[], layer: LayerDescriptor): boolean { +function isGeoShapeAggLayer( + indexPatterns: Array>, + layer: LayerDescriptor +): boolean { if (layer.sourceDescriptor === null) { return false; } @@ -176,7 +184,7 @@ function isGeoShapeAggLayer(indexPatterns: IIndexPattern[], layer: LayerDescript function getGeoShapeAggCount( layerLists: LayerDescriptor[][], - indexPatterns: IIndexPattern[] + indexPatterns: Array> ): number { const countsPerMap: number[] = layerLists.map((layerList: LayerDescriptor[]) => { const geoShapeAggLayers = layerList.filter((layerDescriptor) => { @@ -204,7 +212,7 @@ export function buildMapsTelemetry({ settings, }: { mapSavedObjects: MapSavedObject[]; - indexPatternSavedObjects: IIndexPattern[]; + indexPatternSavedObjects: Array>; settings: SavedObjectAttribute; }): SavedObjectAttributes { const layerLists: LayerDescriptor[][] = getLayerLists(mapSavedObjects); @@ -283,10 +291,12 @@ export async function getMapsTelemetry(config: MapsConfigType) { const savedObjectsClient = getInternalRepository(); // @ts-ignore const mapSavedObjects: MapSavedObject[] = await getMapSavedObjects(savedObjectsClient); - const indexPatternSavedObjects: IIndexPattern[] = (await getIndexPatternSavedObjects( + const indexPatternSavedObjects: Array> = (await getIndexPatternSavedObjects( // @ts-ignore savedObjectsClient - )) as IIndexPattern[]; + )) as Array>; const settings: SavedObjectAttribute = { showMapVisualizationTypes: config.showMapVisualizationTypes, }; diff --git a/x-pack/plugins/maps/server/mvt/__tests__/json/0_0_0_gridagg.json b/x-pack/plugins/maps/server/mvt/__tests__/json/0_0_0_gridagg.json new file mode 100644 index 0000000000000..0945dc57fa512 --- /dev/null +++ b/x-pack/plugins/maps/server/mvt/__tests__/json/0_0_0_gridagg.json @@ -0,0 +1 @@ +{"took":2,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":{"value":10000,"relation":"gte"},"max_score":null,"hits":[]},"aggregations":{"gridSplit":{"buckets":[{"key":"7/37/48","doc_count":42637,"avg_of_TOTAL_AV":{"value":5398920.390458991},"gridCentroid":{"location":{"lat":40.77936432658204,"lon":-73.96795676049909},"count":42637}}]}}} diff --git a/x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0.pbf b/x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0_docs.pbf similarity index 100% rename from x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0.pbf rename to x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0_docs.pbf diff --git a/x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0_grid_asgrid.pbf b/x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0_grid_asgrid.pbf new file mode 100644 index 0000000000000..f2289865b8022 Binary files /dev/null and b/x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0_grid_asgrid.pbf differ diff --git a/x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0_grid_aspoint.pbf b/x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0_grid_aspoint.pbf new file mode 100644 index 0000000000000..54b0791ccd136 Binary files /dev/null and b/x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0_grid_aspoint.pbf differ diff --git a/x-pack/plugins/maps/server/mvt/__tests__/tile_searches.ts b/x-pack/plugins/maps/server/mvt/__tests__/tile_es_responses.ts similarity index 57% rename from x-pack/plugins/maps/server/mvt/__tests__/tile_searches.ts rename to x-pack/plugins/maps/server/mvt/__tests__/tile_es_responses.ts index 317d6434cf81e..9fbaba21e71d5 100644 --- a/x-pack/plugins/maps/server/mvt/__tests__/tile_searches.ts +++ b/x-pack/plugins/maps/server/mvt/__tests__/tile_es_responses.ts @@ -7,10 +7,6 @@ import * as path from 'path'; import * as fs from 'fs'; -const search000path = path.resolve(__dirname, './json/0_0_0_search.json'); -const search000raw = fs.readFileSync(search000path); -const search000json = JSON.parse((search000raw as unknown) as string); - export const TILE_SEARCHES = { '0.0.0': { countResponse: { @@ -22,7 +18,18 @@ export const TILE_SEARCHES = { failed: 0, }, }, - searchResponse: search000json, + searchResponse: loadJson('./json/0_0_0_search.json'), + }, +}; + +export const TILE_GRIDAGGS = { + '0.0.0': { + gridAggResponse: loadJson('./json/0_0_0_gridagg.json'), }, - '1.1.0': {}, }; + +function loadJson(filePath: string) { + const absolutePath = path.resolve(__dirname, filePath); + const rawContents = fs.readFileSync(absolutePath); + return JSON.parse((rawContents as unknown) as string); +} diff --git a/x-pack/plugins/maps/server/mvt/get_tile.test.ts b/x-pack/plugins/maps/server/mvt/get_tile.test.ts index b9c928d594539..76c1741ab2ad0 100644 --- a/x-pack/plugins/maps/server/mvt/get_tile.test.ts +++ b/x-pack/plugins/maps/server/mvt/get_tile.test.ts @@ -4,11 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getTile } from './get_tile'; -import { TILE_SEARCHES } from './__tests__/tile_searches'; +import { getGridTile, getTile } from './get_tile'; +import { TILE_GRIDAGGS, TILE_SEARCHES } from './__tests__/tile_es_responses'; import { Logger } from 'src/core/server'; import * as path from 'path'; import * as fs from 'fs'; +import { ES_GEO_FIELD_TYPE, RENDER_AS } from '../../common/constants'; describe('getTile', () => { const mockCallElasticsearch = jest.fn(); @@ -51,13 +52,84 @@ describe('getTile', () => { callElasticsearch: mockCallElasticsearch, }); - if (tile === null) { - throw new Error('Tile should be created'); - } + compareTiles('./__tests__/pbf/0_0_0_docs.pbf', tile); + }); +}); + +describe('getGridTile', () => { + const mockCallElasticsearch = jest.fn(); + + const geometryFieldName = 'geometry'; + + // For mock-purposes only. The ES-call response is mocked in 0_0_0_gridagg.json file + const requestBody = { + _source: { excludes: [] }, + aggs: { + gridSplit: { + aggs: { + // eslint-disable-next-line @typescript-eslint/naming-convention + avg_of_TOTAL_AV: { avg: { field: 'TOTAL_AV' } }, + gridCentroid: { geo_centroid: { field: geometryFieldName } }, + }, + geotile_grid: { + bounds: null, + field: geometryFieldName, + precision: null, + shard_size: 65535, + size: 65535, + }, + }, + }, + docvalue_fields: [], + query: { + bool: { + filter: [], + }, + }, + script_fields: {}, + size: 0, + stored_fields: ['*'], + }; - const expectedPath = path.resolve(__dirname, './__tests__/pbf/0_0_0.pbf'); - const expectedBin = fs.readFileSync(expectedPath, 'binary'); - const expectedTile = Buffer.from(expectedBin, 'binary'); - expect(expectedTile.equals(tile)).toBe(true); + beforeEach(() => { + mockCallElasticsearch.mockReset(); + mockCallElasticsearch.mockImplementation((type) => { + return TILE_GRIDAGGS['0.0.0'].gridAggResponse; + }); + }); + + const defaultParams = { + x: 0, + y: 0, + z: 0, + index: 'manhattan', + requestBody, + geometryFieldName, + logger: ({ + info: () => {}, + } as unknown) as Logger, + callElasticsearch: mockCallElasticsearch, + requestType: RENDER_AS.POINT, + geoFieldType: ES_GEO_FIELD_TYPE.GEO_POINT, + }; + + test('0.0.0 tile (clusters)', async () => { + const tile = await getGridTile(defaultParams); + compareTiles('./__tests__/pbf/0_0_0_grid_aspoint.pbf', tile); + }); + + test('0.0.0 tile (grids)', async () => { + const tile = await getGridTile({ ...defaultParams, requestType: RENDER_AS.GRID }); + compareTiles('./__tests__/pbf/0_0_0_grid_asgrid.pbf', tile); }); }); + +function compareTiles(expectedRelativePath: string, actualTile: Buffer | null) { + if (actualTile === null) { + throw new Error('Tile should be created'); + } + const expectedPath = path.resolve(__dirname, expectedRelativePath); + const expectedBin = fs.readFileSync(expectedPath, 'binary'); + const expectedTile = Buffer.from(expectedBin, 'binary'); + expect(expectedTile.equals(actualTile)).toBe(true); +} diff --git a/x-pack/plugins/maps/server/mvt/get_tile.ts b/x-pack/plugins/maps/server/mvt/get_tile.ts index 9621f7f174a30..dd88be7f80c2e 100644 --- a/x-pack/plugins/maps/server/mvt/get_tile.ts +++ b/x-pack/plugins/maps/server/mvt/get_tile.ts @@ -13,22 +13,89 @@ import { Feature, FeatureCollection, Polygon } from 'geojson'; import { ES_GEO_FIELD_TYPE, FEATURE_ID_PROPERTY_NAME, + GEOTILE_GRID_AGG_NAME, KBN_TOO_MANY_FEATURES_PROPERTY, + MAX_ZOOM, MVT_SOURCE_LAYER_NAME, + RENDER_AS, + SUPER_FINE_ZOOM_DELTA, } from '../../common/constants'; -import { hitsToGeoJson } from '../../common/elasticsearch_geo_utils'; +import { hitsToGeoJson } from '../../common/elasticsearch_util'; import { flattenHit } from './util'; +import { convertRegularRespToGeoJson } from '../../common/elasticsearch_util'; +import { ESBounds, tile2lat, tile2long, tileToESBbox } from '../../common/geo_tile_utils'; -interface ESBounds { - top_left: { - lon: number; - lat: number; - }; - bottom_right: { - lon: number; - lat: number; - }; +export async function getGridTile({ + logger, + callElasticsearch, + index, + geometryFieldName, + x, + y, + z, + requestBody = {}, + requestType = RENDER_AS.POINT, + geoFieldType = ES_GEO_FIELD_TYPE.GEO_POINT, +}: { + x: number; + y: number; + z: number; + geometryFieldName: string; + index: string; + callElasticsearch: (type: string, ...args: any[]) => Promise; + logger: Logger; + requestBody: any; + requestType: RENDER_AS; + geoFieldType: ES_GEO_FIELD_TYPE; +}): Promise { + const esBbox: ESBounds = tileToESBbox(x, y, z); + try { + let bboxFilter; + if (geoFieldType === ES_GEO_FIELD_TYPE.GEO_POINT) { + bboxFilter = { + geo_bounding_box: { + [geometryFieldName]: esBbox, + }, + }; + } else if (geoFieldType === ES_GEO_FIELD_TYPE.GEO_SHAPE) { + const geojsonPolygon = tileToGeoJsonPolygon(x, y, z); + bboxFilter = { + geo_shape: { + [geometryFieldName]: { + shape: geojsonPolygon, + relation: 'INTERSECTS', + }, + }, + }; + } else { + throw new Error(`${geoFieldType} is not valid geo field-type`); + } + requestBody.query.bool.filter.push(bboxFilter); + + requestBody.aggs[GEOTILE_GRID_AGG_NAME].geotile_grid.precision = Math.min( + z + SUPER_FINE_ZOOM_DELTA, + MAX_ZOOM + ); + requestBody.aggs[GEOTILE_GRID_AGG_NAME].geotile_grid.bounds = esBbox; + + const esGeotileGridQuery = { + index, + body: requestBody, + }; + + const gridAggResult = await callElasticsearch('search', esGeotileGridQuery); + const features: Feature[] = convertRegularRespToGeoJson(gridAggResult, requestType); + const featureCollection: FeatureCollection = { + features, + type: 'FeatureCollection', + }; + + return createMvtTile(featureCollection, z, x, y); + } catch (e) { + logger.warn(`Cannot generate grid-tile for ${z}/${x}/${y}: ${e.message}`); + return null; + } } export async function getTile({ @@ -149,26 +216,7 @@ export async function getTile({ type: 'FeatureCollection', }; - const tileIndex = geojsonvt(featureCollection, { - maxZoom: 24, // max zoom to preserve detail on; can't be higher than 24 - tolerance: 3, // simplification tolerance (higher means simpler) - extent: 4096, // tile extent (both width and height) - buffer: 64, // tile buffer on each side - debug: 0, // logging level (0 to disable, 1 or 2) - lineMetrics: false, // whether to enable line metrics tracking for LineString/MultiLineString features - promoteId: null, // name of a feature property to promote to feature.id. Cannot be used with `generateId` - generateId: false, // whether to generate feature ids. Cannot be used with `promoteId` - indexMaxZoom: 5, // max zoom in the initial tile index - indexMaxPoints: 100000, // max number of points per tile in the index - }); - const tile = tileIndex.getTile(z, x, y); - - if (tile) { - const pbf = vtpbf.fromGeojsonVt({ [MVT_SOURCE_LAYER_NAME]: tile }, { version: 2 }); - return Buffer.from(pbf); - } else { - return null; - } + return createMvtTile(featureCollection, z, x, y); } catch (e) { logger.warn(`Cannot generate tile for ${z}/${x}/${y}: ${e.message}`); return null; @@ -195,15 +243,6 @@ function tileToGeoJsonPolygon(x: number, y: number, z: number): Polygon { }; } -function tile2long(x: number, z: number): number { - return (x / Math.pow(2, z)) * 360 - 180; -} - -function tile2lat(y: number, z: number): number { - const n = Math.PI - (2 * Math.PI * y) / Math.pow(2, z); - return (180 / Math.PI) * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))); -} - function esBboxToGeoJsonPolygon(esBounds: ESBounds): Polygon { let minLon = esBounds.top_left.lon; const maxLon = esBounds.bottom_right.lon; @@ -224,3 +263,31 @@ function esBboxToGeoJsonPolygon(esBounds: ESBounds): Polygon { ], }; } + +function createMvtTile( + featureCollection: FeatureCollection, + z: number, + x: number, + y: number +): Buffer | null { + const tileIndex = geojsonvt(featureCollection, { + maxZoom: 24, // max zoom to preserve detail on; can't be higher than 24 + tolerance: 3, // simplification tolerance (higher means simpler) + extent: 4096, // tile extent (both width and height) + buffer: 64, // tile buffer on each side + debug: 0, // logging level (0 to disable, 1 or 2) + lineMetrics: false, // whether to enable line metrics tracking for LineString/MultiLineString features + promoteId: null, // name of a feature property to promote to feature.id. Cannot be used with `generateId` + generateId: false, // whether to generate feature ids. Cannot be used with `promoteId` + indexMaxZoom: 5, // max zoom in the initial tile index + indexMaxPoints: 100000, // max number of points per tile in the index + }); + const tile = tileIndex.getTile(z, x, y); + + if (tile) { + const pbf = vtpbf.fromGeojsonVt({ [MVT_SOURCE_LAYER_NAME]: tile }, { version: 2 }); + return Buffer.from(pbf); + } else { + return null; + } +} diff --git a/x-pack/plugins/maps/server/mvt/mvt_routes.ts b/x-pack/plugins/maps/server/mvt/mvt_routes.ts index 32c14a355ba2a..266a240b53017 100644 --- a/x-pack/plugins/maps/server/mvt/mvt_routes.ts +++ b/x-pack/plugins/maps/server/mvt/mvt_routes.ts @@ -6,10 +6,21 @@ import rison from 'rison-node'; import { schema } from '@kbn/config-schema'; -import { Logger } from 'src/core/server'; +import { + KibanaRequest, + KibanaResponseFactory, + Logger, + RequestHandlerContext, +} from 'src/core/server'; import { IRouter } from 'src/core/server'; -import { MVT_GETTILE_API_PATH, API_ROOT_PATH } from '../../common/constants'; -import { getTile } from './get_tile'; +import { + MVT_GETTILE_API_PATH, + API_ROOT_PATH, + MVT_GETGRIDTILE_API_PATH, + ES_GEO_FIELD_TYPE, + RENDER_AS, +} from '../../common/constants'; +import { getGridTile, getTile } from './get_tile'; const CACHE_TIMEOUT = 0; // Todo. determine good value. Unsure about full-implications (e.g. wrt. time-based data). @@ -28,46 +39,93 @@ export function initMVTRoutes({ router, logger }: { logger: Logger; router: IRou }), }, }, - async (context, request, response) => { + async ( + context: RequestHandlerContext, + request: KibanaRequest, unknown>, + response: KibanaResponseFactory + ) => { const { query } = request; + const requestBodyDSL = rison.decode(query.requestBody as string); - const callElasticsearch = async (type: string, ...args: any[]): Promise => { - return await context.core.elasticsearch.legacy.client.callAsCurrentUser(type, ...args); - }; + const tile = await getTile({ + logger, + callElasticsearch: makeCallElasticsearch(context), + geometryFieldName: query.geometryFieldName as string, + x: query.x as number, + y: query.y as number, + z: query.z as number, + index: query.index as string, + requestBody: requestBodyDSL as any, + }); - const requestBodyDSL = rison.decode(query.requestBody); + return sendResponse(response, tile); + } + ); - const tile = await getTile({ + router.get( + { + path: `${API_ROOT_PATH}/${MVT_GETGRIDTILE_API_PATH}`, + validate: { + query: schema.object({ + x: schema.number(), + y: schema.number(), + z: schema.number(), + geometryFieldName: schema.string(), + requestBody: schema.string(), + index: schema.string(), + requestType: schema.string(), + geoFieldType: schema.string(), + }), + }, + }, + async ( + context: RequestHandlerContext, + request: KibanaRequest, unknown>, + response: KibanaResponseFactory + ) => { + const { query } = request; + const requestBodyDSL = rison.decode(query.requestBody as string); + + const tile = await getGridTile({ logger, - callElasticsearch, - geometryFieldName: query.geometryFieldName, - x: query.x, - y: query.y, - z: query.z, - index: query.index, - requestBody: requestBodyDSL, + callElasticsearch: makeCallElasticsearch(context), + geometryFieldName: query.geometryFieldName as string, + x: query.x as number, + y: query.y as number, + z: query.z as number, + index: query.index as string, + requestBody: requestBodyDSL as any, + requestType: query.requestType as RENDER_AS, + geoFieldType: query.geoFieldType as ES_GEO_FIELD_TYPE, }); - if (tile) { - return response.ok({ - body: tile, - headers: { - 'content-disposition': 'inline', - 'content-length': `${tile.length}`, - 'Content-Type': 'application/x-protobuf', - 'Cache-Control': `max-age=${CACHE_TIMEOUT}`, - }, - }); - } else { - return response.ok({ - headers: { - 'content-disposition': 'inline', - 'content-length': '0', - 'Content-Type': 'application/x-protobuf', - 'Cache-Control': `max-age=${CACHE_TIMEOUT}`, - }, - }); - } + return sendResponse(response, tile); } ); } + +function sendResponse(response: KibanaResponseFactory, tile: any) { + const headers = { + 'content-disposition': 'inline', + 'content-length': tile ? `${tile.length}` : `0`, + 'Content-Type': 'application/x-protobuf', + 'Cache-Control': `max-age=${CACHE_TIMEOUT}`, + }; + + if (tile) { + return response.ok({ + body: tile, + headers, + }); + } else { + return response.ok({ + headers, + }); + } +} + +function makeCallElasticsearch(context: RequestHandlerContext) { + return async (type: string, ...args: any[]): Promise => { + return context.core.elasticsearch.legacy.client.callAsCurrentUser(type, ...args); + }; +} 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 161dde51df43e..1c8bfafeb10ff 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 @@ -12,6 +12,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 { useRefreshAnalyticsList, @@ -130,19 +131,25 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { const indexPatternName = destinationIndex; try { - const newIndexPattern = await mlContext.indexPatterns.make(); + await mlContext.indexPatterns.createAndSave( + { + title: indexPatternName, + }, + false, + true + ); - Object.assign(newIndexPattern, { - id: '', - title: indexPatternName, + addRequestMessage({ + message: i18n.translate( + 'xpack.ml.dataframe.analytics.create.createIndexPatternSuccessMessage', + { + defaultMessage: 'Kibana index pattern {indexPatternName} created.', + values: { indexPatternName }, + } + ), }); - - const id = await newIndexPattern.create(); - - await mlContext.indexPatterns.clearCache(); - - // id returns false if there's a duplicate index pattern. - if (id === false) { + } catch (e) { + if (e instanceof DuplicateIndexPatternError) { addRequestMessage({ error: i18n.translate( 'xpack.ml.dataframe.analytics.create.duplicateIndexPatternErrorMessageError', @@ -158,34 +165,17 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { } ), }); - return; - } - - // check if there's a default index pattern, if not, - // set the newly created one as the default index pattern. - if (!mlContext.kibanaConfig.get('defaultIndex')) { - await mlContext.kibanaConfig.set('defaultIndex', id); + } else { + addRequestMessage({ + error: extractErrorMessage(e), + message: i18n.translate( + 'xpack.ml.dataframe.analytics.create.createIndexPatternErrorMessage', + { + defaultMessage: 'An error occurred creating the Kibana index pattern:', + } + ), + }); } - - addRequestMessage({ - message: i18n.translate( - 'xpack.ml.dataframe.analytics.create.createIndexPatternSuccessMessage', - { - defaultMessage: 'Kibana index pattern {indexPatternName} created.', - values: { indexPatternName }, - } - ), - }); - } catch (e) { - addRequestMessage({ - error: extractErrorMessage(e), - message: i18n.translate( - 'xpack.ml.dataframe.analytics.create.createIndexPatternErrorMessage', - { - defaultMessage: 'An error occurred creating the Kibana index pattern:', - } - ), - }); } }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js index 08b61a5fa4eed..2ad0c9b1ac263 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js @@ -215,7 +215,7 @@ export class ImportView extends Component { // mappings, use this field as the time field. // This relies on the field being populated by // the ingest pipeline on ingest - if (mappings[DEFAULT_TIME_FIELD] !== undefined) { + if (mappings.properties[DEFAULT_TIME_FIELD] !== undefined) { timeFieldName = DEFAULT_TIME_FIELD; this.setState({ timeFieldName }); } @@ -615,34 +615,16 @@ export class ImportView extends Component { } } -async function createKibanaIndexPattern( - indexPatternName, - indexPatterns, - timeFieldName, - kibanaConfig -) { +async function createKibanaIndexPattern(indexPatternName, indexPatterns, timeFieldName) { try { - const emptyPattern = await indexPatterns.make(); - - Object.assign(emptyPattern, { - id: '', + const emptyPattern = await indexPatterns.createAndSave({ title: indexPatternName, timeFieldName, }); - const id = await emptyPattern.create(); - - await indexPatterns.clearCache(); - - // check if there's a default index pattern, if not, - // set the newly created one as the default index pattern. - if (!kibanaConfig.get('defaultIndex')) { - await kibanaConfig.set('defaultIndex', id); - } - return { success: true, - id, + id: emptyPattern.id, }; } catch (error) { return { diff --git a/x-pack/plugins/ml/public/application/settings/calendars/edit/calendar_form/__snapshots__/calendar_form.test.js.snap b/x-pack/plugins/ml/public/application/settings/calendars/edit/calendar_form/__snapshots__/calendar_form.test.js.snap index fed435d47dfc6..ad76bb9115617 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/edit/calendar_form/__snapshots__/calendar_form.test.js.snap +++ b/x-pack/plugins/ml/public/application/settings/calendars/edit/calendar_form/__snapshots__/calendar_form.test.js.snap @@ -56,6 +56,7 @@ exports[`CalendarForm Renders calendar form 1`] = ` labelType="label" > - +

{description}

@@ -116,6 +116,7 @@ export const CalendarForm = ({ value={calendarId} onChange={onCalendarIdChange} disabled={isEdit === true || saving === true} + data-test-subj="mlCalendarIdInput" />
@@ -132,6 +133,7 @@ export const CalendarForm = ({ value={description} onChange={onDescriptionChange} disabled={isEdit === true || saving === true} + data-test-subj="mlCalendarDescriptionInput" /> diff --git a/x-pack/plugins/ml/public/application/settings/calendars/edit/new_event_modal/new_event_modal.js b/x-pack/plugins/ml/public/application/settings/calendars/edit/new_event_modal/new_event_modal.js index d80e248674a8f..0b5d2b7b5a3ea 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/edit/new_event_modal/new_event_modal.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/edit/new_event_modal/new_event_modal.js @@ -257,7 +257,12 @@ export class NewEventModal extends Component { return ( - + @@ -293,13 +299,18 @@ export class NewEventModal extends Component { - + - + c.calendar_id).join(', '), + }} /> } onCancel={this.closeDestroyModal} @@ -130,18 +135,7 @@ export class CalendarsListUI extends Component { } buttonColor="danger" defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} - > -

- c.calendar_id).join(', '), - }} - /> -

-
+ /> ); } diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/__snapshots__/add_item_popover.test.js.snap b/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/__snapshots__/add_item_popover.test.js.snap index 6e9cd17deabee..969406724537d 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/__snapshots__/add_item_popover.test.js.snap +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/__snapshots__/add_item_popover.test.js.snap @@ -7,7 +7,7 @@ exports[`AddItemPopover calls addItems with multiple items on clicking Add butto button={ @@ -71,6 +72,7 @@ exports[`AddItemPopover calls addItems with multiple items on clicking Add butto grow={false} > @@ -93,7 +95,7 @@ exports[`AddItemPopover opens the popover onButtonClick 1`] = ` button={ @@ -157,6 +160,7 @@ exports[`AddItemPopover opens the popover onButtonClick 1`] = ` grow={false} > @@ -179,7 +183,7 @@ exports[`AddItemPopover renders the popover 1`] = ` button={ @@ -243,6 +248,7 @@ exports[`AddItemPopover renders the popover 1`] = ` grow={false} > diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/add_item_popover.js b/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/add_item_popover.js index 07e060d87b36a..53a3877e2f1bd 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/add_item_popover.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/add_item_popover.js @@ -84,7 +84,7 @@ export class AddItemPopover extends Component { iconSide="right" onClick={this.onButtonClick} isDisabled={this.props.canCreateFilter === false} - data-test-subj="mlFilterListAddItemButton" + data-test-subj="mlFilterListOpenNewItemsPopoverButton" > } > - + @@ -127,6 +131,7 @@ export class AddItemPopover extends Component { } + data-test-subj="mlFilterListDeleteConfirmation" defaultFocusedButton="confirm" onCancel={[Function]} onConfirm={[Function]} diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.js b/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.js index 75fdce8e2bac8..5aafe79645f6a 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.js @@ -86,6 +86,7 @@ export class DeleteFilterListModal extends Component { } buttonColor="danger" defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} + data-test-subj={'mlFilterListDeleteConfirmation'} /> ); diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/components/edit_description_popover/__snapshots__/edit_description_popover.test.js.snap b/x-pack/plugins/ml/public/application/settings/filter_lists/components/edit_description_popover/__snapshots__/edit_description_popover.test.js.snap index 9904e90a5afae..268b93923a432 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/components/edit_description_popover/__snapshots__/edit_description_popover.test.js.snap +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/components/edit_description_popover/__snapshots__/edit_description_popover.test.js.snap @@ -47,6 +47,7 @@ exports[`FilterListUsagePopover opens the popover onButtonClick 1`] = ` labelType="label" > diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/components/edit_description_popover/edit_description_popover.js b/x-pack/plugins/ml/public/application/settings/filter_lists/components/edit_description_popover/edit_description_popover.js index 06ace034ca819..b7bcb201f2438 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/components/edit_description_popover/edit_description_popover.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/components/edit_description_popover/edit_description_popover.js @@ -102,6 +102,7 @@ export class EditDescriptionPopover extends Component { name="filter_list_description" value={value} onChange={this.onChange} + data-test-subj={'mlFilterListDescriptionInput'} /> diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/edit/__snapshots__/edit_filter_list.test.js.snap b/x-pack/plugins/ml/public/application/settings/filter_lists/edit/__snapshots__/edit_filter_list.test.js.snap index c2fab64473228..f6a4f76975553 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/edit/__snapshots__/edit_filter_list.test.js.snap +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/edit/__snapshots__/edit_filter_list.test.js.snap @@ -80,6 +80,7 @@ exports[`EditFilterList adds new items to filter list 1`] = ` grow={false} > - +

A test filter list

@@ -180,6 +183,7 @@ exports[`EditFilterListHeader renders the header when creating a new filter list labelType="label" > - +

A test filter list

diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.js b/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.js index 681c54ca9eee0..9ea470a388f02 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.js @@ -362,7 +362,10 @@ export class EditFilterListUI extends Component { /> - this.returnToFiltersList()}> + this.returnToFiltersList()} + > updateNewFilterId(e.target.value)} + data-test-subj={'mlNewFilterListIdInput'} /> ); @@ -96,7 +97,7 @@ export const EditFilterListHeader = ({ if (description !== undefined && description.length > 0) { descriptionField = ( - +

{description}

); diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/list/table.js b/x-pack/plugins/ml/public/application/settings/filter_lists/list/table.js index ed992b4e866ff..9e1457483cb2c 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/list/table.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/list/table.js @@ -214,7 +214,7 @@ export function FilterListsTable({ isSelectable={true} data-test-subj="mlFilterListsTable" rowProps={(item) => ({ - 'data-test-subj': `mlFilterListsRow row-${item.filter_id}`, + 'data-test-subj': `mlFilterListRow row-${item.filter_id}`, })} /> diff --git a/x-pack/plugins/ml/public/application/util/index_utils.ts b/x-pack/plugins/ml/public/application/util/index_utils.ts index 192552b25d15a..42be3dd8252f9 100644 --- a/x-pack/plugins/ml/public/application/util/index_utils.ts +++ b/x-pack/plugins/ml/public/application/util/index_utils.ts @@ -104,7 +104,11 @@ export function getQueryFromSavedSearch(savedSearch: SavedSearchSavedObject) { export function getIndexPatternById(id: string): Promise { if (indexPatternsContract !== null) { - return indexPatternsContract.get(id); + if (id) { + return indexPatternsContract.get(id); + } else { + return indexPatternsContract.create({}); + } } else { throw new Error('Index patterns are not initialized!'); } diff --git a/x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts b/x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts index d1a4df768a6ae..394dff1408134 100644 --- a/x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts +++ b/x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts @@ -5,13 +5,13 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; -import { IIndexPattern } from 'src/plugins/data/server'; +import { IndexPatternAttributes } from 'src/plugins/data/server'; export class IndexPatternHandler { constructor(private savedObjectsClient: SavedObjectsClientContract) {} // returns a id based on an index pattern name async getIndexPatternId(indexName: string) { - const response = await this.savedObjectsClient.find({ + const response = await this.savedObjectsClient.find({ type: 'index-pattern', perPage: 10, search: `"${indexName}"`, diff --git a/x-pack/plugins/monitoring/public/angular/app_modules.ts b/x-pack/plugins/monitoring/public/angular/app_modules.ts index 499610045d771..4ef905fd35fc4 100644 --- a/x-pack/plugins/monitoring/public/angular/app_modules.ts +++ b/x-pack/plugins/monitoring/public/angular/app_modules.ts @@ -41,10 +41,6 @@ import { licenseProvider } from '../services/license'; // @ts-ignore import { titleProvider } from '../services/title'; // @ts-ignore -import { monitoringBeatsBeatProvider } from '../directives/beats/beat'; -// @ts-ignore -import { monitoringBeatsOverviewProvider } from '../directives/beats/overview'; -// @ts-ignore import { monitoringMlListingProvider } from '../directives/elasticsearch/ml_job_listing'; // @ts-ignore import { monitoringMainProvider } from '../directives/main'; @@ -153,8 +149,6 @@ function createMonitoringAppServices() { function createMonitoringAppDirectives() { angular .module('monitoring/directives', []) - .directive('monitoringBeatsBeat', monitoringBeatsBeatProvider) - .directive('monitoringBeatsOverview', monitoringBeatsOverviewProvider) .directive('monitoringMlListing', monitoringMlListingProvider) .directive('monitoringMain', monitoringMainProvider); } diff --git a/x-pack/plugins/monitoring/public/components/apm/instance/instance.js b/x-pack/plugins/monitoring/public/components/apm/instance/instance.js index 396d2258edd0c..eec24e741ac41 100644 --- a/x-pack/plugins/monitoring/public/components/apm/instance/instance.js +++ b/x-pack/plugins/monitoring/public/components/apm/instance/instance.js @@ -42,9 +42,7 @@ export function ApmServerInstance({ summary, metrics, ...props }) { const charts = seriesToShow.map((data, index) => ( - - - + )); @@ -55,15 +53,15 @@ export function ApmServerInstance({ summary, metrics, ...props }) {

+ + + + - - - - {charts} diff --git a/x-pack/plugins/monitoring/public/components/apm/instances/instances.js b/x-pack/plugins/monitoring/public/components/apm/instances/instances.js index 6dcfa6dd043aa..e05ba1878caed 100644 --- a/x-pack/plugins/monitoring/public/components/apm/instances/instances.js +++ b/x-pack/plugins/monitoring/public/components/apm/instances/instances.js @@ -156,11 +156,11 @@ export function ApmServerInstances({ apms, setupMode }) { /> + + + + - - - - {setupModeCallout} ( - - - + )); @@ -51,15 +49,15 @@ export function ApmOverview({ stats, metrics, ...props }) {

+ + + + - - - - {charts} diff --git a/x-pack/plugins/monitoring/public/components/beats/beat/beat.js b/x-pack/plugins/monitoring/public/components/beats/beat/beat.js index 3fe211c0f2edc..f489271659bfe 100644 --- a/x-pack/plugins/monitoring/public/components/beats/beat/beat.js +++ b/x-pack/plugins/monitoring/public/components/beats/beat/beat.js @@ -135,6 +135,9 @@ export function Beat({ summary, metrics, ...props }) { + + + diff --git a/x-pack/plugins/monitoring/public/components/beats/listing/listing.js b/x-pack/plugins/monitoring/public/components/beats/listing/listing.js index be8595e8e6bbe..60a35e00a4c63 100644 --- a/x-pack/plugins/monitoring/public/components/beats/listing/listing.js +++ b/x-pack/plugins/monitoring/public/components/beats/listing/listing.js @@ -13,6 +13,7 @@ import { EuiSpacer, EuiLink, EuiScreenReaderOnly, + EuiPanel, } from '@elastic/eui'; import { Stats } from '../../beats'; import { formatMetric } from '../../../lib/format_number'; @@ -153,9 +154,11 @@ export class Listing extends PureComponent { /> - + - + + + {setupModeCallOut} - + + + + - - -

- -

-
- - -
+ +

+ +

+
+ +
- - -

- -

-
- - -
+ +

+ +

+
+ +
- - -

- -

-
- - -
+ +

+ +

+
+ +
- +
+ + @@ -212,18 +213,25 @@ exports[`Overview that overview page shows a message if there is no beats data 1 /> - + + + + - + + + diff --git a/x-pack/plugins/monitoring/public/components/beats/overview/overview.js b/x-pack/plugins/monitoring/public/components/beats/overview/overview.js index 83f92ea1b481c..897f017f44f41 100644 --- a/x-pack/plugins/monitoring/public/components/beats/overview/overview.js +++ b/x-pack/plugins/monitoring/public/components/beats/overview/overview.js @@ -30,46 +30,40 @@ function renderLatestActive(latestActive, latestTypes, latestVersions) { return ( - - -

- -

-
- - -
+ +

+ +

+
+ +
- - -

- -

-
- - -
+ +

+ +

+
+ +
- - -

- -

-
- - -
+ +

+ +

+
+ +
); @@ -118,10 +112,13 @@ export function BeatsOverview({ /> - + - {renderLatestActive(latestActive, latestTypes, latestVersions)} - + + + {renderLatestActive(latestActive, latestTypes, latestVersions)} + + {charts}
diff --git a/x-pack/plugins/monitoring/public/components/cluster/overview/apm_panel.js b/x-pack/plugins/monitoring/public/components/cluster/overview/apm_panel.js index ccbf0b0ec711d..4bf07710393ea 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/overview/apm_panel.js +++ b/x-pack/plugins/monitoring/public/components/cluster/overview/apm_panel.js @@ -55,7 +55,7 @@ export function ApmPanel(props) { {...props} url="apm" title={i18n.translate('xpack.monitoring.cluster.overview.apmPanel.apmTitle', { - defaultMessage: 'APM', + defaultMessage: 'APM server', })} > @@ -70,21 +70,21 @@ export function ApmPanel(props) { aria-label={i18n.translate( 'xpack.monitoring.cluster.overview.apmPanel.overviewLinkAriaLabel', { - defaultMessage: 'APM Overview', + defaultMessage: 'APM server overview', } )} data-test-subj="apmOverview" > - + {formatMetric(props.totalEvents, '0.[0]a')} - + {apmsTotal} }} /> @@ -144,7 +144,7 @@ export function ApmPanel(props) {
- + - + {formatMetric(props.totalEvents, '0.[0]a')} - + {props.logs.types.map((log, index) => ( - + - + @@ -276,7 +277,7 @@ export function ElasticsearchPanel(props) {
- + - + - + - + {showMlJobs()} - + - + - + - + - + {formatNumber(get(indices, 'docs.count'), 'int_commas')} - + - + - + - +
diff --git a/x-pack/plugins/monitoring/public/components/cluster/overview/kibana_panel.js b/x-pack/plugins/monitoring/public/components/cluster/overview/kibana_panel.js index 6fa533302db48..7df0a3ca7138e 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/overview/kibana_panel.js +++ b/x-pack/plugins/monitoring/public/components/cluster/overview/kibana_panel.js @@ -111,7 +111,7 @@ export function KibanaPanel(props) { data-test-subj="kibana_overview" data-overview-status={props.status} > - + {props.requests_total} - + - + {formatNumber(props.concurrent_connections, 'int_commas')} - + - + {formatNumber(props.events_in_total, '0.[0]a')} - + - + {props.max_uptime ? formatNumber(props.max_uptime, 'time_since') : 0} - + - + {queueTypes[LOGSTASH.QUEUE_TYPES.MEMORY] || 0} - + } checked={showSystemIndices} diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/__tests__/__snapshots__/cells.test.js.snap b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/__tests__/__snapshots__/cells.test.js.snap index c7081dc439085..b0b5ceb46d16c 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/__tests__/__snapshots__/cells.test.js.snap +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/__tests__/__snapshots__/cells.test.js.snap @@ -10,30 +10,46 @@ exports[`Node Listing Metric Cell should format N/A as the metric for an offline exports[`Node Listing Metric Cell should format a non-percentage metric 1`] = `
+
- - 206.3 GB  - - -
- 206.5 GB max -
- 206.3 GB min +
+
+
+
+
+
+
+
+
+ 206.3 GB +
+
@@ -41,30 +57,46 @@ exports[`Node Listing Metric Cell should format a non-percentage metric 1`] = ` exports[`Node Listing Metric Cell should format a percentage metric 1`] = `
+
- - 0%  - - -
- 2% max -
- 0% min +
+
+
+
+
+
+
+
+
+ 0% +
+
diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/__tests__/cells.test.js b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/__tests__/cells.test.js index 0c4b4b2b3c3f4..f0b131b65433c 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/__tests__/cells.test.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/__tests__/cells.test.js @@ -27,6 +27,7 @@ describe('Node Listing Metric Cell', () => { }, summary: { minVal: 0, maxVal: 2, lastVal: 0, slope: -1 }, }, + 'data-test-subj': 'testCell', }; expect(renderWithIntl()).toMatchSnapshot(); }); @@ -54,6 +55,7 @@ describe('Node Listing Metric Cell', () => { slope: -1, }, }, + 'data-test-subj': 'testCell2', }; expect(renderWithIntl()).toMatchSnapshot(); }); diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/cells.js b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/cells.js index 4c3b642213d99..9956dd4da7d8a 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/cells.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/cells.js @@ -4,19 +4,42 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useState } from 'react'; import { get } from 'lodash'; import { formatMetric } from '../../../lib/format_number'; -import { EuiText, EuiTitle, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { + EuiText, + EuiPopover, + EuiIcon, + EuiDescriptionList, + EuiSpacer, + EuiKeyboardAccessible, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +const TRENDING_DOWN = i18n.translate('xpack.monitoring.elasticsearch.node.cells.trendingDownText', { + defaultMessage: 'down', +}); +const TRENDING_UP = i18n.translate('xpack.monitoring.elasticsearch.node.cells.trendingUpText', { + defaultMessage: 'up', +}); + function OfflineCell() { return
N/A
; } -const getSlopeArrow = (slope) => { +const getDirection = (slope) => { + if (slope || slope === 0) { + return slope > 0 ? TRENDING_UP : TRENDING_DOWN; + } + return null; +}; + +const getIcon = (slope) => { if (slope || slope === 0) { - return slope > 0 ? 'up' : 'down'; + return slope > 0 ? 'arrowUp' : 'arrowDown'; } return null; }; @@ -28,40 +51,82 @@ const metricVal = (metric, format, isPercent, units) => { return formatMetric(metric, format, units); }; -const noWrapStyle = { overflowX: 'hidden', whiteSpace: 'nowrap' }; - function MetricCell({ isOnline, metric = {}, isPercent, ...props }) { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const onButtonClick = () => setIsPopoverOpen((isPopoverOpen) => !isPopoverOpen); + const closePopover = () => setIsPopoverOpen(false); + if (isOnline) { const { lastVal, maxVal, minVal, slope } = get(metric, 'summary', {}); const format = get(metric, 'metric.format'); const units = get(metric, 'metric.units'); + const tooltipItems = [ + { + title: i18n.translate('xpack.monitoring.elasticsearch.node.cells.tooltip.trending', { + defaultMessage: 'Trending', + }), + description: getDirection(slope), + }, + { + title: i18n.translate('xpack.monitoring.elasticsearch.node.cells.tooltip.max', { + defaultMessage: 'Max value', + }), + description: metricVal(maxVal, format, isPercent, units), + }, + { + title: i18n.translate('xpack.monitoring.elasticsearch.node.cells.tooltip.min', { + defaultMessage: 'Min value', + }), + description: metricVal(minVal, format, isPercent, units), + }, + ]; + + const button = ( + + + + ); + return ( - + + - - - {metricVal(lastVal, format, isPercent)} -   - - - - - {i18n.translate('xpack.monitoring.elasticsearch.nodes.cells.maxText', { - defaultMessage: '{metric} max', - values: { - metric: metricVal(maxVal, format, isPercent, units), - }, - })} - - - {i18n.translate('xpack.monitoring.elasticsearch.nodes.cells.minText', { - defaultMessage: '{metric} min', - values: { - metric: metricVal(minVal, format, isPercent, units), - }, - })} - + + + +
+ + + + {i18n.translate('xpack.monitoring.elasticsearch.node.cells.tooltip.preface', { + defaultMessage: 'Applies to current time period', + })} + +
+
+
+ + {metricVal(lastVal, format, isPercent)} + +
); diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js index 43512f8e528f6..f088f7c0d348a 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js @@ -73,7 +73,6 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, aler name: i18n.translate('xpack.monitoring.elasticsearch.nodes.nameColumnTitle', { defaultMessage: 'Name', }), - width: '20%', field: 'name', sortable: true, render: (value, node) => { @@ -131,7 +130,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, aler defaultMessage: 'Alerts', }), field: 'alerts', - width: '175px', + // width: '175px', sortable: true, render: (_field, node) => { return ( @@ -148,6 +147,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, aler name: i18n.translate('xpack.monitoring.elasticsearch.nodes.statusColumnTitle', { defaultMessage: 'Status', }), + dataType: 'boolean', field: 'isOnline', sortable: true, render: (value) => { @@ -181,22 +181,18 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, aler name: i18n.translate('xpack.monitoring.elasticsearch.nodes.shardsColumnTitle', { defaultMessage: 'Shards', }), + dataType: 'number', field: 'shardCount', sortable: true, render: (value, node) => { - return node.isOnline ? ( -
- {value} -
- ) : ( - - ); + return node.isOnline ? {value} : ; }, }); if (showCgroupMetricsElasticsearch) { cols.push({ name: cpuUsageColumnTitle, + dataType: 'number', field: 'node_cgroup_quota', sortable: getSortHandler('node_cgroup_quota'), render: (value, node) => ( @@ -213,6 +209,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, aler name: i18n.translate('xpack.monitoring.elasticsearch.nodes.cpuThrottlingColumnTitle', { defaultMessage: 'CPU Throttling', }), + dataType: 'number', field: 'node_cgroup_throttled', sortable: getSortHandler('node_cgroup_throttled'), render: (value, node) => ( @@ -227,6 +224,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, aler } else { cols.push({ name: cpuUsageColumnTitle, + dataType: 'number', field: 'node_cpu_utilization', sortable: getSortHandler('node_cpu_utilization'), render: (value, node) => { @@ -245,6 +243,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, aler name: i18n.translate('xpack.monitoring.elasticsearch.nodes.loadAverageColumnTitle', { defaultMessage: 'Load Average', }), + dataType: 'number', field: 'node_load_average', sortable: getSortHandler('node_load_average'), render: (value, node) => ( @@ -265,6 +264,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, aler javaVirtualMachine: 'JVM', }, }), + dataType: 'number', field: 'node_jvm_mem_percent', sortable: getSortHandler('node_jvm_mem_percent'), render: (value, node) => ( @@ -281,6 +281,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, aler name: i18n.translate('xpack.monitoring.elasticsearch.nodes.diskFreeSpaceColumnTitle', { defaultMessage: 'Disk Free Space', }), + dataType: 'number', field: 'node_free_space', sortable: getSortHandler('node_free_space'), render: (value, node) => ( diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/table_head.js b/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/table_head.js index fd5f28ea02039..3c875667fe04c 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/table_head.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/table_head.js @@ -5,6 +5,7 @@ */ import React from 'react'; +import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiFlexItem, EuiSwitch } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -37,7 +38,12 @@ class IndexLabel extends React.Component { $el && $el[0] && unmountComponentAtNode($el[0])); - - scope.$watch('data', (data = {}) => { - render( - , - $el[0] - ); - }); - }, - }; -} diff --git a/x-pack/plugins/monitoring/public/directives/beats/overview/index.js b/x-pack/plugins/monitoring/public/directives/beats/overview/index.js deleted file mode 100644 index 4faf69e13d02c..0000000000000 --- a/x-pack/plugins/monitoring/public/directives/beats/overview/index.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { BeatsOverview } from '../../../components/beats/overview'; - -export function monitoringBeatsOverviewProvider() { - return { - restrict: 'E', - scope: { - data: '=', - onBrush: '<', - zoomInfo: '<', - }, - link(scope, $el) { - scope.$on('$destroy', () => $el && $el[0] && unmountComponentAtNode($el[0])); - - scope.$watch('data', (data = {}) => { - render( - , - $el[0] - ); - }); - }, - }; -} diff --git a/x-pack/plugins/monitoring/public/directives/main/index.html b/x-pack/plugins/monitoring/public/directives/main/index.html index fabd207d72b1f..fb24d9e678d56 100644 --- a/x-pack/plugins/monitoring/public/directives/main/index.html +++ b/x-pack/plugins/monitoring/public/directives/main/index.html @@ -1,19 +1,32 @@
-
- - +
+
+
+
+
+
+

{{pageTitle || monitoringMain.instance}}

+
+
+
+
+
+ + +
+
`; 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 62880e7510cd2..0cb721bb5382f 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 @@ -15,6 +15,8 @@ import { Title } from './title'; import { DraggableArguments, BadgeOptions, TitleProp } from './types'; import { useFormatUrl } from '../link_to'; import { SecurityPageName } from '../../../app/types'; +import { Sourcerer } from '../sourcerer'; +import { SourcererScopeName } from '../../store/sourcerer/model'; interface HeaderProps { border?: boolean; @@ -72,6 +74,7 @@ export interface HeaderPageProps extends HeaderProps { badgeOptions?: BadgeOptions; children?: React.ReactNode; draggableArguments?: DraggableArguments; + hideSourcerer?: boolean; subtitle?: SubtitleProps['items']; subtitle2?: SubtitleProps['items']; title: TitleProp; @@ -84,6 +87,7 @@ const HeaderPageComponent: React.FC = ({ border, children, draggableArguments, + hideSourcerer = false, isLoading, subtitle, subtitle2, @@ -138,6 +142,7 @@ const HeaderPageComponent: React.FC = ({ )} + {!hideSourcerer && } ); }; diff --git a/x-pack/plugins/security_solution/public/common/components/last_event_time/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/last_event_time/index.test.tsx index 9473ba67a1c4f..c2800b0705b43 100644 --- a/x-pack/plugins/security_solution/public/common/components/last_event_time/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/last_event_time/index.test.tsx @@ -37,7 +37,7 @@ describe('Last Event Time Stat', () => { ]); const wrapper = mount( - + ); expect(wrapper.html()).toBe( @@ -54,7 +54,7 @@ describe('Last Event Time Stat', () => { ]); const wrapper = mount( - + ); expect(wrapper.html()).toBe('Last event: 12 minutes ago'); @@ -69,7 +69,7 @@ describe('Last Event Time Stat', () => { ]); const wrapper = mount( - + ); @@ -85,7 +85,7 @@ describe('Last Event Time Stat', () => { ]); const wrapper = mount( - + ); expect(wrapper.html()).toContain(getEmptyValue()); diff --git a/x-pack/plugins/security_solution/public/common/components/last_event_time/index.tsx b/x-pack/plugins/security_solution/public/common/components/last_event_time/index.tsx index e9e8e7a03017c..d508040f84239 100644 --- a/x-pack/plugins/security_solution/public/common/components/last_event_time/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/last_event_time/index.tsx @@ -8,58 +8,65 @@ import { EuiIcon, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { memo } from 'react'; +import { DocValueFields } from '../../../../common/search_strategy'; import { LastEventIndexKey } from '../../../graphql/types'; import { useTimelineLastEventTime } from '../../containers/events/last_event_time'; import { getEmptyTagValue } from '../empty_value'; import { FormattedRelativePreferenceDate } from '../formatted_date'; export interface LastEventTimeProps { + docValueFields: DocValueFields[]; hostName?: string; indexKey: LastEventIndexKey; ip?: string; + indexNames: string[]; } -export const LastEventTime = memo(({ hostName, indexKey, ip }) => { - const [loading, { lastSeen, errorMessage }] = useTimelineLastEventTime({ - indexKey, - details: { - hostName, - ip, - }, - }); +export const LastEventTime = memo( + ({ docValueFields, hostName, indexKey, ip, indexNames }) => { + const [loading, { lastSeen, errorMessage }] = useTimelineLastEventTime({ + docValueFields, + indexKey, + indexNames, + details: { + hostName, + ip, + }, + }); + + if (errorMessage != null) { + return ( + + + + ); + } - if (errorMessage != null) { return ( - - - + <> + {loading && } + {!loading && lastSeen != null && new Date(lastSeen).toString() === 'Invalid Date' + ? lastSeen + : !loading && + lastSeen != null && ( + , + }} + /> + )} + {!loading && lastSeen == null && getEmptyTagValue()} + ); } - - return ( - <> - {loading && } - {!loading && lastSeen != null && new Date(lastSeen).toString() === 'Invalid Date' - ? lastSeen - : !loading && - lastSeen != null && ( - , - }} - /> - )} - {!loading && lastSeen == null && getEmptyTagValue()} - - ); -}); +); LastEventTime.displayName = 'LastEventTime'; diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx index 7286c6b743692..99dc8a802b33d 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx @@ -47,6 +47,7 @@ describe('Matrix Histogram Component', () => { errorMessage: 'error', histogramType: MatrixHistogramType.alerts, id: 'mockId', + indexNames: [], isInspected: false, isPtrIncluded: false, setQuery: jest.fn(), diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx index 485ca4c93133a..e7d7e60a3c408 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx @@ -37,7 +37,6 @@ export type MatrixHistogramComponentProps = MatrixHistogramProps & hideHistogramIfEmpty?: boolean; histogramType: MatrixHistogramType; id: string; - indexToAdd?: string[] | null; legendPosition?: Position; mapping?: MatrixHistogramMappingTypes; showSpacer?: boolean; @@ -72,7 +71,7 @@ export const MatrixHistogramComponent: React.FC = histogramType, hideHistogramIfEmpty = false, id, - indexToAdd, + indexNames, legendPosition, mapping, panelHeight = DEFAULT_PANEL_HEIGHT, @@ -136,7 +135,7 @@ export const MatrixHistogramComponent: React.FC = errorMessage, filterQuery, histogramType, - indexToAdd, + indexNames, startDate, stackByField: selectedStackByOption.value, }); diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts index fc1df4d8ca85f..9a892110bde43 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts @@ -59,6 +59,7 @@ interface MatrixHistogramBasicProps { export interface MatrixHistogramQueryProps { endDate: string; errorMessage: string; + indexNames: string[]; filterQuery?: ESQuery | string | undefined; setAbsoluteRangeDatePicker?: ActionCreator<{ id: InputsModelId; @@ -68,7 +69,6 @@ export interface MatrixHistogramQueryProps { setAbsoluteRangeDatePickerTarget?: InputsModelId; stackByField: string; startDate: string; - indexToAdd?: string[] | null; histogramType: MatrixHistogramType; } diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts index 89aa77106933e..da5099f61e9b2 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts @@ -105,6 +105,7 @@ const getMockObject = ( }, }, }, + sourcerer: {}, }); const getUrlForAppMock = (appId: string, options?: { path?: string; absolute?: boolean }) => @@ -130,7 +131,7 @@ describe('Navigation Breadcrumbs', () => { }, { href: - "securitySolution:hosts?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution:hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", text: 'Hosts', }, { @@ -150,7 +151,7 @@ describe('Navigation Breadcrumbs', () => { { text: 'Network', href: - "securitySolution:network?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution:network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: 'Flows', @@ -169,7 +170,7 @@ describe('Navigation Breadcrumbs', () => { { text: 'Timelines', href: - "securitySolution:timelines?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution:timelines?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, ]); }); @@ -184,12 +185,12 @@ describe('Navigation Breadcrumbs', () => { { text: 'Hosts', href: - "securitySolution:hosts?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution:hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: 'siem-kibana', href: - "securitySolution:hosts/siem-kibana?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution:hosts/siem-kibana?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: 'Authentications', href: '' }, ]); @@ -205,11 +206,11 @@ describe('Navigation Breadcrumbs', () => { { text: 'Network', href: - "securitySolution:network?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution:network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: ipv4, - href: `securitySolution:network/ip/${ipv4}/source?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, + href: `securitySolution:network/ip/${ipv4}/source?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, }, { text: 'Flows', href: '' }, ]); @@ -225,11 +226,11 @@ describe('Navigation Breadcrumbs', () => { { text: 'Network', href: - "securitySolution:network?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution:network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: ipv6, - href: `securitySolution:network/ip/${ipv6Encoded}/source?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, + href: `securitySolution:network/ip/${ipv6Encoded}/source?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, }, { text: 'Flows', href: '' }, ]); @@ -245,7 +246,7 @@ describe('Navigation Breadcrumbs', () => { { text: 'Detections', href: - "securitySolution:detections?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution:detections?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, ]); }); @@ -259,7 +260,7 @@ describe('Navigation Breadcrumbs', () => { { text: 'Cases', href: - "securitySolution:case?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution:case?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, ]); }); @@ -280,11 +281,11 @@ describe('Navigation Breadcrumbs', () => { { text: 'Cases', href: - "securitySolution:case?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution:case?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: sampleCase.name, - href: `securitySolution:case/${sampleCase.id}?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, + href: `securitySolution:case/${sampleCase.id}?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, }, ]); }); @@ -311,12 +312,12 @@ describe('Navigation Breadcrumbs', () => { { text: 'Hosts', href: - "securitySolution:hosts?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution:hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: 'siem-kibana', href: - "securitySolution:hosts/siem-kibana?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution:hosts/siem-kibana?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: 'Authentications', href: '' }, ]); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/helpers.ts b/x-pack/plugins/security_solution/public/common/components/navigation/helpers.ts index 8f5a3ac63fa1a..ed71f55fd0161 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/helpers.ts @@ -19,12 +19,19 @@ import { import { Query, Filter } from '../../../../../../../src/plugins/data/public'; import { SearchNavTab } from './types'; +import { SourcererScopePatterns } from '../../store/sourcerer/model'; export const getSearch = (tab: SearchNavTab, urlState: UrlState): string => { if (tab && tab.urlKey != null && URL_STATE_KEYS[tab.urlKey] != null) { return URL_STATE_KEYS[tab.urlKey].reduce( (myLocation: Location, urlKey: KeyUrlState) => { - let urlStateToReplace: UrlInputsModel | Query | Filter[] | TimelineUrl | string = ''; + let urlStateToReplace: + | Filter[] + | Query + | SourcererScopePatterns + | TimelineUrl + | UrlInputsModel + | string = ''; if (urlKey === CONSTANTS.appQuery && urlState.query != null) { if (urlState.query.query === '') { @@ -40,6 +47,8 @@ export const getSearch = (tab: SearchNavTab, urlState: UrlState): string => { } } else if (urlKey === CONSTANTS.timerange) { urlStateToReplace = urlState[CONSTANTS.timerange]; + } else if (urlKey === CONSTANTS.sourcerer) { + urlStateToReplace = urlState[CONSTANTS.sourcerer]; } else if (urlKey === CONSTANTS.timeline && urlState[CONSTANTS.timeline] != null) { const timeline = urlState[CONSTANTS.timeline]; if (timeline.id === '') { diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx index 16cb19f5a0c14..102ed7851e57d 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx @@ -78,6 +78,7 @@ describe('SIEM Navigation', () => { }, [CONSTANTS.appQuery]: { query: '', language: 'kuery' }, [CONSTANTS.filters]: [], + [CONSTANTS.sourcerer]: {}, [CONSTANTS.timeline]: { id: '', isOpen: false, @@ -145,6 +146,7 @@ describe('SIEM Navigation', () => { pageName: 'hosts', pathName: '/', search: '', + sourcerer: {}, state: undefined, tabName: 'authentications', query: { query: '', language: 'kuery' }, @@ -252,6 +254,7 @@ describe('SIEM Navigation', () => { query: { language: 'kuery', query: '' }, savedQuery: undefined, search: '', + sourcerer: {}, state: undefined, tabName: 'authentications', timeline: { id: '', isOpen: false, graphEventId: '' }, diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx index 5ee35e7da0f3e..b149488ff38a7 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx @@ -40,19 +40,20 @@ export const SiemNavigationComponent: React.FC< if (pathName || pageName) { setBreadcrumbs( { - query: urlState.query, detailName, filters: urlState.filters, + flowTarget, navTabs, pageName, pathName, + query: urlState.query, savedQuery: urlState.savedQuery, search, + sourcerer: urlState.sourcerer, + state, tabName, - flowTarget, - timerange: urlState.timerange, timeline: urlState.timeline, - state, + timerange: urlState.timerange, }, chrome, getUrlForApp @@ -69,6 +70,7 @@ export const SiemNavigationComponent: React.FC< navTabs={navTabs} pageName={pageName} pathName={pathName} + sourcerer={urlState.sourcerer} savedQuery={urlState.savedQuery} tabName={tabName} timeline={urlState.timeline} diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx index b25cf3779801b..5c69edbabdc66 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx @@ -68,6 +68,7 @@ describe('Tab Navigation', () => { }, [CONSTANTS.appQuery]: { query: 'host.name:"siem-es"', language: 'kuery' }, [CONSTANTS.filters]: [], + [CONSTANTS.sourcerer]: {}, [CONSTANTS.timeline]: { id: '', isOpen: false, @@ -126,6 +127,7 @@ describe('Tab Navigation', () => { }, [CONSTANTS.appQuery]: { query: 'host.name:"siem-es"', language: 'kuery' }, [CONSTANTS.filters]: [], + [CONSTANTS.sourcerer]: {}, [CONSTANTS.timeline]: { id: '', isOpen: false, diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx index 217ad0e58570f..3eb66b5591b85 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx @@ -94,10 +94,17 @@ export const TabNavigationComponent = (props: TabNavigationProps) => { () => Object.values(navTabs).map((tab) => { const isSelected = selectedTabId === tab.id; - const { query, filters, savedQuery, timerange, timeline } = props; - const search = getSearch(tab, { query, filters, savedQuery, timerange, timeline }); + const { filters, query, savedQuery, sourcerer, timeline, timerange } = props; + const search = getSearch(tab, { + filters, + query, + savedQuery, + sourcerer, + timeline, + timerange, + }); const hrefWithSearch = - tab.href + getSearch(tab, { query, filters, savedQuery, timerange, timeline }); + tab.href + getSearch(tab, { filters, query, savedQuery, sourcerer, timeline, timerange }); return ( { - const original = jest.requireActual('../../containers/sourcerer'); +const mockDispatch = jest.fn(); +jest.mock('react-redux', () => { + const original = jest.requireActual('react-redux'); return { ...original, - useManageSource: () => mockManageSource, + useDispatch: () => mockDispatch, }; }); const mockOptions = [ - { label: 'auditbeat-*', key: 'auditbeat-*-0', value: 'auditbeat-*', checked: 'on' }, - { label: 'endgame-*', key: 'endgame-*-1', value: 'endgame-*', checked: 'on' }, - { label: 'filebeat-*', key: 'filebeat-*-2', value: 'filebeat-*', checked: 'on' }, - { label: 'logs-*', key: 'logs-*-3', value: 'logs-*', checked: 'on' }, - { label: 'packetbeat-*', key: 'packetbeat-*-4', value: 'packetbeat-*', checked: undefined }, - { label: 'winlogbeat-*', key: 'winlogbeat-*-5', value: 'winlogbeat-*', checked: 'on' }, - { - label: 'apm-*-transaction*', - key: 'apm-*-transaction*-0', - value: 'apm-*-transaction*', - disabled: true, - checked: undefined, - }, - { - label: 'blobbeat-*', - key: 'blobbeat-*-1', - value: 'blobbeat-*', - disabled: true, - checked: undefined, - }, + { label: 'apm-*-transaction*', value: 'apm-*-transaction*' }, + { label: 'auditbeat-*', value: 'auditbeat-*' }, + { label: 'endgame-*', value: 'endgame-*' }, + { label: 'filebeat-*', value: 'filebeat-*' }, + { label: 'logs-*', value: 'logs-*' }, + { label: 'packetbeat-*', value: 'packetbeat-*' }, + { label: 'winlogbeat-*', value: 'winlogbeat-*' }, ]; +const defaultProps = { + scope: sourcererModel.SourcererScopeName.default, +}; describe('Sourcerer component', () => { + beforeEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + }); + const state: State = mockGlobalState; + const { storage } = createSecuritySolutionStorageMock(); + let store = createStore( + state, + SUB_PLUGINS_REDUCER, + apolloClientObservable, + kibanaObservable, + storage + ); + + beforeEach(() => { + store = createStore( + state, + SUB_PLUGINS_REDUCER, + apolloClientObservable, + kibanaObservable, + storage + ); + }); + // Using props callback instead of simulating clicks, // because EuiSelectable uses a virtualized list, which isn't easily testable via test subjects - it('Mounts with correct options selected and disabled', () => { - const wrapper = mount(); + it('Mounts with all options selected', () => { + const wrapper = mount( + + + + ); wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); - expect( - wrapper.find(`[data-test-subj="indexPattern-switcher"]`).first().prop('options') + wrapper.find(`[data-test-subj="indexPattern-switcher"]`).first().prop('selectedOptions') ).toEqual(mockOptions); }); - it('onChange calls updateSourceGroupIndicies', () => { - const wrapper = mount(); - wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); - - const switcherOnChange = wrapper - .find(`[data-test-subj="indexPattern-switcher"]`) - .first() - .prop('onChange'); - // @ts-ignore - switcherOnChange([mockOptions[0], mockOptions[1]]); - expect(updateSourceGroupIndicies).toHaveBeenCalledWith(SecurityPageName.default, [ - mockOptions[0].value, - mockOptions[1].value, - ]); - }); - it('Disabled options have icon tooltip', () => { - const wrapper = mount(); - wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); - // @ts-ignore - const Rendered = wrapper - .find(`[data-test-subj="indexPattern-switcher"]`) - .first() - .prop('renderOption')( - { - label: 'blobbeat-*', - key: 'blobbeat-*-1', - value: 'blobbeat-*', - disabled: true, - checked: undefined, + it('Mounts with some options selected', () => { + const state2 = { + ...mockGlobalState, + sourcerer: { + ...mockGlobalState.sourcerer, + sourcererScopes: { + ...mockGlobalState.sourcerer.sourcererScopes, + [SourcererScopeName.default]: { + ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.default], + loading: false, + selectedPatterns: [DEFAULT_INDEX_PATTERN[0]], + }, + }, }, - '' + }; + + store = createStore( + state2, + SUB_PLUGINS_REDUCER, + apolloClientObservable, + kibanaObservable, + storage + ); + const wrapper = mount( + + + ); - expect(Rendered.props.children[1].props.content).toEqual(i18n.DISABLED_INDEX_PATTERNS); + wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); + expect( + wrapper.find(`[data-test-subj="indexPattern-switcher"]`).first().prop('selectedOptions') + ).toEqual([mockOptions[0]]); }); - - it('Button links to index path', () => { - const wrapper = mount(); + it('onChange calls updateSourcererScopeIndices', async () => { + const wrapper = mount( + + + + ); + expect(true).toBeTruthy(); wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); - expect(wrapper.find(`[data-test-subj="add-index"]`).first().prop('href')).toEqual( - ADD_INDEX_PATH + await act(async () => { + ((wrapper.find(EuiComboBox).props() as unknown) as { + onChange: (a: EuiComboBoxOptionOption[]) => void; + }).onChange([mockOptions[0], mockOptions[1]]); + await waitFor(() => { + wrapper.update(); + }); + }); + wrapper.find(`[data-test-subj="add-index"]`).first().simulate('click'); + + expect(mockDispatch).toHaveBeenCalledWith( + sourcererActions.setSelectedIndexPatterns({ + id: SourcererScopeName.default, + selectedPatterns: [mockOptions[0].value, mockOptions[1].value], + }) ); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.tsx b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.tsx index 6275ce19c3608..7a74f5bf2247f 100644 --- a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.tsx @@ -4,50 +4,122 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback, useMemo, useState } from 'react'; import { EuiButton, EuiButtonEmpty, - EuiHighlight, - EuiIconTip, + EuiComboBox, + EuiComboBoxOptionOption, + EuiIcon, + EuiFlexGroup, + EuiFlexItem, EuiPopover, - EuiPopoverFooter, EuiPopoverTitle, - EuiSelectable, + EuiSpacer, + EuiText, + EuiToolTip, } from '@elastic/eui'; -import { EuiSelectableOption } from '@elastic/eui/src/components/selectable/selectable_option'; -import { useManageSource } from '../../containers/sourcerer'; +import deepEqual from 'fast-deep-equal'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import styled from 'styled-components'; + import * as i18n from './translations'; import { SOURCERER_FEATURE_FLAG_ON } from '../../containers/sourcerer/constants'; -import { ADD_INDEX_PATH } from '../../../../common/constants'; - -export const MaybeSourcerer = React.memo(() => { - const { - activeSourceGroupId, - availableIndexPatterns, - getManageSourceGroupById, - isIndexPatternsLoading, - updateSourceGroupIndicies, - } = useManageSource(); - const { defaultPatterns, indexPatterns: selectedOptions, loading: loadingIndices } = useMemo( - () => getManageSourceGroupById(activeSourceGroupId), - [getManageSourceGroupById, activeSourceGroupId] +import { sourcererActions, sourcererModel } from '../../store/sourcerer'; +import { State } from '../../store'; +import { getSourcererScopeSelector, SourcererScopeSelector } from './selectors'; + +const PopoverContent = styled.div` + width: 600px; +`; + +const ResetButton = styled(EuiButtonEmpty)` + width: fit-content; +`; +interface SourcererComponentProps { + scope: sourcererModel.SourcererScopeName; +} + +export const SourcererComponent = React.memo(({ scope: scopeId }) => { + const dispatch = useDispatch(); + const sourcererScopeSelector = useMemo(getSourcererScopeSelector, []); + const { configIndexPatterns, kibanaIndexPatterns, sourcererScope } = useSelector< + State, + SourcererScopeSelector + >((state) => sourcererScopeSelector(state, scopeId), deepEqual); + const { selectedPatterns, loading } = sourcererScope; + const [isPopoverOpen, setPopoverIsOpen] = useState(false); + const [selectedOptions, setSelectedOptions] = useState>>( + selectedPatterns.map((indexSelected) => ({ + label: indexSelected, + value: indexSelected, + })) ); - const loading = useMemo(() => loadingIndices || isIndexPatternsLoading, [ - isIndexPatternsLoading, - loadingIndices, - ]); + const setPopoverIsOpenCb = useCallback(() => setPopoverIsOpen((prevState) => !prevState), []); const onChangeIndexPattern = useCallback( - (newIndexPatterns: string[]) => { - updateSourceGroupIndicies(activeSourceGroupId, newIndexPatterns); + (newSelectedPatterns: string[]) => { + dispatch( + sourcererActions.setSelectedIndexPatterns({ + id: scopeId, + selectedPatterns: newSelectedPatterns, + }) + ); }, - [activeSourceGroupId, updateSourceGroupIndicies] + [dispatch, scopeId] + ); + + const renderOption = useCallback( + (option) => { + const { value } = option; + if (kibanaIndexPatterns.some((kip) => kip.title === value)) { + return ( + <> + {value} + + ); + } + return <>{value}; + }, + [kibanaIndexPatterns] + ); + + const onChangeCombo = useCallback((newSelectedOptions) => { + setSelectedOptions(newSelectedOptions); + }, []); + + const resetDataSources = useCallback(() => { + setSelectedOptions( + configIndexPatterns.map((indexSelected) => ({ + label: indexSelected, + value: indexSelected, + })) + ); + }, [configIndexPatterns]); + + const handleSaveIndices = useCallback(() => { + onChangeIndexPattern(selectedOptions.map((so) => so.label)); + setPopoverIsOpen(false); + }, [onChangeIndexPattern, selectedOptions]); + + const handleClosePopOver = useCallback(() => { + setPopoverIsOpen(false); + }, []); + + const indexesPatternOptions = useMemo( + () => + [...configIndexPatterns, ...kibanaIndexPatterns.map((kip) => kip.title)].reduce< + Array> + >((acc, index) => { + if (index != null && !acc.some((o) => o.label.includes(index))) { + return [...acc, { label: index, value: index }]; + } + return acc; + }, []), + [configIndexPatterns, kibanaIndexPatterns] ); - const [isPopoverOpen, setPopoverIsOpen] = useState(false); - const setPopoverIsOpenCb = useCallback(() => setPopoverIsOpen((prevState) => !prevState), []); const trigger = useMemo( () => ( { data-test-subj="sourcerer-trigger" flush="left" iconSide="right" - iconType="indexSettings" + iconType="arrowDown" + isLoading={loading} onClick={setPopoverIsOpenCb} size="l" title={i18n.SOURCERER} @@ -63,99 +136,91 @@ export const MaybeSourcerer = React.memo(() => { {i18n.SOURCERER} ), - [setPopoverIsOpenCb] - ); - const options: EuiSelectableOption[] = useMemo( - () => - availableIndexPatterns.map((title, id) => ({ - label: title, - key: `${title}-${id}`, - value: title, - checked: selectedOptions.includes(title) ? 'on' : undefined, - })), - [availableIndexPatterns, selectedOptions] + [setPopoverIsOpenCb, loading] ); - const unSelectableOptions: EuiSelectableOption[] = useMemo( - () => - defaultPatterns - .filter((title) => !availableIndexPatterns.includes(title)) - .map((title, id) => ({ - label: title, - key: `${title}-${id}`, - value: title, - disabled: true, - checked: undefined, - })), - [availableIndexPatterns, defaultPatterns] - ); - const renderOption = useCallback( - (option, searchValue) => ( - <> - {option.label} - {option.disabled ? ( - - ) : null} - + + const comboBox = useMemo( + () => ( + ), - [] + [indexesPatternOptions, onChangeCombo, renderOption, selectedOptions] ); - const onChange = useCallback( - (choices: EuiSelectableOption[]) => { - const choice = choices.reduce( - (acc, { checked, label }) => (checked === 'on' ? [...acc, label] : acc), - [] - ); - onChangeIndexPattern(choice); - }, - [onChangeIndexPattern] + + useEffect(() => { + const newSelecteOptions = selectedPatterns.map((indexSelected) => ({ + label: indexSelected, + value: indexSelected, + })); + setSelectedOptions((prevSelectedOptions) => { + if (!deepEqual(newSelecteOptions, prevSelectedOptions)) { + return newSelecteOptions; + } + return prevSelectedOptions; + }); + }, [selectedPatterns]); + + const tooltipContent = useMemo( + () => (isPopoverOpen ? null : sourcererScope.selectedPatterns.sort().join(', ')), + [isPopoverOpen, sourcererScope.selectedPatterns] ); - const allOptions = useMemo(() => [...options, ...unSelectableOptions], [ - options, - unSelectableOptions, - ]); + return ( - setPopoverIsOpen(false)} - display="block" - panelPaddingSize="s" - ownFocus - > -
- - <> - {i18n.CHANGE_INDEX_PATTERNS} - - - - - {(list, search) => ( - <> - {search} - {list} - - )} - - - - {i18n.ADD_INDEX_PATTERNS} - - -
-
+ + + + + <>{i18n.SELECT_INDEX_PATTERNS} + + + {i18n.INDEX_PATTERNS_SELECTION_LABEL} + + {comboBox} + + + + + {i18n.INDEX_PATTERNS_RESET} + + + + + {i18n.SAVE_INDEX_PATTERNS} + + + + + + ); }); -MaybeSourcerer.displayName = 'Sourcerer'; +SourcererComponent.displayName = 'Sourcerer'; -export const Sourcerer = SOURCERER_FEATURE_FLAG_ON ? MaybeSourcerer : () => null; +export const Sourcerer = SOURCERER_FEATURE_FLAG_ON ? SourcererComponent : () => null; diff --git a/x-pack/plugins/security_solution/public/common/components/sourcerer/selectors.tsx b/x-pack/plugins/security_solution/public/common/components/sourcerer/selectors.tsx new file mode 100644 index 0000000000000..6bbe24e921880 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/sourcerer/selectors.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { State } from '../../store'; +import { sourcererSelectors } from '../../store/sourcerer'; +import { KibanaIndexPatterns, ManageScope, SourcererScopeName } from '../../store/sourcerer/model'; + +export interface SourcererScopeSelector { + configIndexPatterns: string[]; + kibanaIndexPatterns: KibanaIndexPatterns; + sourcererScope: ManageScope; +} + +export const getSourcererScopeSelector = () => { + const getKibanaIndexPatternsSelector = sourcererSelectors.kibanaIndexPatternsSelector(); + const getScopesSelector = sourcererSelectors.scopesSelector(); + const getConfigIndexPatternsSelector = sourcererSelectors.configIndexPatternsSelector(); + + const mapStateToProps = (state: State, scopeId: SourcererScopeName): SourcererScopeSelector => { + const kibanaIndexPatterns = getKibanaIndexPatternsSelector(state); + const scope = getScopesSelector(state)[scopeId]; + const configIndexPatterns = getConfigIndexPatternsSelector(state); + + return { + kibanaIndexPatterns, + configIndexPatterns, + sourcererScope: scope, + }; + }; + + return mapStateToProps; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/sourcerer/translations.ts b/x-pack/plugins/security_solution/public/common/components/sourcerer/translations.ts index 71b1734dad6a6..473eb43d5c4fe 100644 --- a/x-pack/plugins/security_solution/public/common/components/sourcerer/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/sourcerer/translations.ts @@ -6,23 +6,26 @@ import { i18n } from '@kbn/i18n'; -export const SOURCERER = i18n.translate('xpack.securitySolution.indexPatterns.sourcerer', { - defaultMessage: 'Sourcerer', +export const SOURCERER = i18n.translate('xpack.securitySolution.indexPatterns.dataSourcesLabel', { + defaultMessage: 'Data sources', }); -export const CHANGE_INDEX_PATTERNS = i18n.translate('xpack.securitySolution.indexPatterns.help', { - defaultMessage: 'Change index patterns', +export const ALL_DEFAULT = i18n.translate('xpack.securitySolution.indexPatterns.allDefault', { + defaultMessage: 'All default', }); -export const ADD_INDEX_PATTERNS = i18n.translate('xpack.securitySolution.indexPatterns.add', { - defaultMessage: 'Configure Kibana index patterns', +export const SELECT_INDEX_PATTERNS = i18n.translate('xpack.securitySolution.indexPatterns.help', { + defaultMessage: 'Data sources selection', }); -export const CONFIGURE_INDEX_PATTERNS = i18n.translate( - 'xpack.securitySolution.indexPatterns.configure', +export const SAVE_INDEX_PATTERNS = i18n.translate('xpack.securitySolution.indexPatterns.save', { + defaultMessage: 'Save', +}); + +export const INDEX_PATTERNS_SELECTION_LABEL = i18n.translate( + 'xpack.securitySolution.indexPatterns.selectionLabel', { - defaultMessage: - 'Configure additional Kibana index patterns to see them become available in the Security Solution', + defaultMessage: 'Choose the source of the data on this page', } ); @@ -33,3 +36,17 @@ export const DISABLED_INDEX_PATTERNS = i18n.translate( 'Disabled index patterns are recommended on this page, but first need to be configured in your Kibana index pattern settings', } ); + +export const INDEX_PATTERNS_RESET = i18n.translate( + 'xpack.securitySolution.indexPatterns.resetButton', + { + defaultMessage: 'Reset', + } +); + +export const PICK_INDEX_PATTERNS = i18n.translate( + 'xpack.securitySolution.indexPatterns.pickIndexPatternsCombo', + { + defaultMessage: 'Pick index patterns', + } +); diff --git a/x-pack/plugins/security_solution/public/common/components/stat_items/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/stat_items/index.test.tsx index 664d8b2ff5598..310d4c52ec5bc 100644 --- a/x-pack/plugins/security_solution/public/common/components/stat_items/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/stat_items/index.test.tsx @@ -39,8 +39,10 @@ import { } from '../../mock'; import { State, createStore } from '../../store'; import { Provider as ReduxStoreProvider } from 'react-redux'; -import { KpiHostsData } from '../../../graphql/types'; -import { NetworkKpiStrategyResponse } from '../../../../common/search_strategy'; +import { + HostsKpiStrategyResponse, + NetworkKpiStrategyResponse, +} from '../../../../common/search_strategy'; const from = '2019-06-15T06:00:00.000Z'; const to = '2019-06-18T06:00:00.000Z'; @@ -242,7 +244,7 @@ describe('useKpiMatrixStatus', () => { data, }: { fieldsMapping: Readonly; - data: NetworkKpiStrategyResponse | KpiHostsData; + data: NetworkKpiStrategyResponse | HostsKpiStrategyResponse; }) => { const statItemsProps: StatItemsProps[] = useKpiMatrixStatus( fieldsMapping, diff --git a/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx b/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx index 13a93a784a2c9..34fb344eed3c4 100644 --- a/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx @@ -18,8 +18,10 @@ import { get, getOr } from 'lodash/fp'; import React, { useState, useEffect } from 'react'; import styled from 'styled-components'; -import { NetworkKpiStrategyResponse } from '../../../../common/search_strategy'; -import { KpiHostsData } from '../../../graphql/types'; +import { + HostsKpiStrategyResponse, + NetworkKpiStrategyResponse, +} from '../../../../common/search_strategy'; import { AreaChart } from '../charts/areachart'; import { BarChart } from '../charts/barchart'; import { ChartSeriesData, ChartData, ChartSeriesConfigs, UpdateDateRange } from '../charts/common'; @@ -113,12 +115,12 @@ export const barchartConfigs = (config?: { onElementClick?: ElementClickListener export const addValueToFields = ( fields: StatItem[], - data: KpiHostsData | NetworkKpiStrategyResponse + data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse ): StatItem[] => fields.map((field) => ({ ...field, value: get(field.key, data) })); export const addValueToAreaChart = ( fields: StatItem[], - data: KpiHostsData | NetworkKpiStrategyResponse + data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse ): ChartSeriesData[] => fields .filter((field) => get(`${field.key}Histogram`, data) != null) @@ -130,7 +132,7 @@ export const addValueToAreaChart = ( export const addValueToBarChart = ( fields: StatItem[], - data: KpiHostsData | NetworkKpiStrategyResponse + data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse ): ChartSeriesData[] => { if (fields.length === 0) return []; return fields.reduce((acc: ChartSeriesData[], field: StatItem, idx: number) => { @@ -159,7 +161,7 @@ export const addValueToBarChart = ( export const useKpiMatrixStatus = ( mappings: Readonly, - data: KpiHostsData | NetworkKpiStrategyResponse, + data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse, id: string, from: string, to: string, diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/helpers.ts b/x-pack/plugins/security_solution/public/common/components/top_n/helpers.ts index b654eaf17b47b..79cbd87cda201 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/components/top_n/helpers.ts @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EventType } from '../../../timelines/store/timeline/model'; +import { TimelineEventsType } from '../../../../common/types/timeline'; import * as i18n from './translations'; export interface TopNOption { inputDisplay: string; - value: EventType; + value: TimelineEventsType; 'data-test-subj': string; } @@ -52,8 +52,8 @@ export const defaultOptions = [...rawEvents, ...alertEvents]; * is always in sync with the `EventType` chosen by the user in * the active timeline. */ -export const getOptions = (activeTimelineEventType?: EventType): TopNOption[] => { - switch (activeTimelineEventType) { +export const getOptions = (activeTimelineEventsType?: TimelineEventsType): TopNOption[] => { + switch (activeTimelineEventsType) { case 'all': return allEvents; case 'raw': diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx index 31318122eb564..594bffbd4ff63 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx @@ -168,6 +168,17 @@ const store = createStore( storage ); +let testProps = { + browserFields: mockBrowserFields, + field, + indexNames: [], + indexPattern: mockIndexPattern, + timelineId: TimelineId.hostsPageExternalAlerts, + toggleTopN: jest.fn(), + onFilterAdded: jest.fn(), + value, +}; + describe('StatefulTopN', () => { // Suppress warnings about "react-beautiful-dnd" /* eslint-disable no-console */ @@ -189,16 +200,7 @@ describe('StatefulTopN', () => { wrapper = mount( - + ); @@ -277,19 +279,14 @@ describe('StatefulTopN', () => { filterManager, }, }; + testProps = { + ...testProps, + timelineId: TimelineId.active, + }; wrapper = mount( - + ); @@ -345,37 +342,33 @@ describe('StatefulTopN', () => { expect(props.to).toEqual('2020-04-15T03:46:09.047Z'); }); }); + describe('rendering in a NON-active timeline context', () => { + test(`defaults to the 'Alert events' option when rendering in a NON-active timeline context (e.g. the Alerts table on the Detections page) when 'documentType' from 'useTimelineTypeContext()' is 'alerts'`, () => { + const filterManager = new FilterManager(mockUiSettingsForFilterManager); - test(`defaults to the 'Alert events' option when rendering in a NON-active timeline context (e.g. the Alerts table on the Detections page) when 'documentType' from 'useTimelineTypeContext()' is 'alerts'`, () => { - const filterManager = new FilterManager(mockUiSettingsForFilterManager); + const manageTimelineForTesting = { + [TimelineId.active]: { + ...getTimelineDefaults(TimelineId.active), + filterManager, + documentType: 'alerts', + }, + }; - const manageTimelineForTesting = { - [TimelineId.active]: { - ...getTimelineDefaults(TimelineId.active), - filterManager, - documentType: 'alerts', - }, - }; - - const wrapper = mount( - - - - - - ); - - const props = wrapper.find('[data-test-subj="top-n"]').first().props() as Props; - - expect(props.defaultView).toEqual('alert'); + testProps = { + ...testProps, + timelineId: TimelineId.detectionsPage, + }; + const wrapper = mount( + + + + + + ); + + const props = wrapper.find('[data-test-subj="top-n"]').first().props() as Props; + + expect(props.defaultView).toEqual('alert'); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx index d71242329bcda..9c81cb57335a5 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx @@ -74,7 +74,7 @@ interface OwnProps { browserFields: BrowserFields; field: string; indexPattern: IIndexPattern; - indexToAdd: string[] | null; + indexNames: string[]; timelineId?: string; toggleTopN: () => void; onFilterAdded?: () => void; @@ -93,7 +93,7 @@ const StatefulTopNComponent: React.FC = ({ dataProviders, field, indexPattern, - indexToAdd, + indexNames, globalFilters = EMPTY_FILTERS, globalQuery = EMPTY_QUERY, kqlMode, @@ -109,7 +109,6 @@ const StatefulTopNComponent: React.FC = ({ const options = getOptions( timelineId === TimelineId.active ? activeTimelineEventType : undefined ); - return ( = ({ filters={timelineId === TimelineId.active ? EMPTY_FILTERS : globalFilters} from={timelineId === TimelineId.active ? activeTimelineFrom : from} indexPattern={indexPattern} - indexToAdd={indexToAdd} + indexNames={indexNames} options={options} query={timelineId === TimelineId.active ? EMPTY_QUERY : globalQuery} setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker} diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx index 667d1816e8f07..829f918ddfe1b 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx @@ -13,6 +13,8 @@ import { setAbsoluteRangeDatePicker } from '../../store/inputs/actions'; import { allEvents, defaultOptions } from './helpers'; import { TopN } from './top_n'; +import { TimelineEventsType } from '../../../../common/types/timeline'; +import { InputsModelId } from '../../store/inputs/constants'; jest.mock('react-router-dom', () => { const original = jest.requireActual('react-router-dom'); @@ -103,29 +105,34 @@ describe('TopN', () => { const query = { query: '', language: 'kuery' }; + const toggleTopN = jest.fn(); + const eventTypes: { [id: string]: TimelineEventsType } = { + raw: 'raw', + alert: 'alert', + all: 'all', + }; + let testProps = { + defaultView: eventTypes.raw, + field, + filters: [], + from: '2020-04-14T00:31:47.695Z', + indexNames: [], + indexPattern: mockIndexPattern, + options: defaultOptions, + query, + setAbsoluteRangeDatePicker, + setAbsoluteRangeDatePickerTarget: 'global' as InputsModelId, + setQuery: jest.fn(), + to: '2020-04-15T00:31:47.695Z', + toggleTopN, + value, + }; describe('common functionality', () => { - let toggleTopN: () => void; let wrapper: ReactWrapper; - beforeEach(() => { - toggleTopN = jest.fn(); wrapper = mount( - + ); }); @@ -143,28 +150,12 @@ describe('TopN', () => { }); describe('events view', () => { - let toggleTopN: () => void; let wrapper: ReactWrapper; beforeEach(() => { - toggleTopN = jest.fn(); wrapper = mount( - + ); }); @@ -181,37 +172,25 @@ describe('TopN', () => { }); describe('alerts view', () => { - let toggleTopN: () => void; let wrapper: ReactWrapper; beforeEach(() => { - toggleTopN = jest.fn(); + testProps = { + ...testProps, + defaultView: eventTypes.alert, + }; wrapper = mount( - + ); }); - test(`it renders SignalsByCategory when defaultView is 'signal'`, () => { + test(`it renders SignalsByCategory when defaultView is 'alert'`, () => { expect(wrapper.find('[data-test-subj="alerts-histogram-panel"]').exists()).toBe(true); }); - test(`it does NOT render EventsByDataset when defaultView is 'signal'`, () => { + test(`it does NOT render EventsByDataset when defaultView is 'alert'`, () => { expect( wrapper.find('[data-test-subj="eventsByDatasetOverview-uuid.v4()Panel"]').exists() ).toBe(false); @@ -222,24 +201,14 @@ describe('TopN', () => { let wrapper: ReactWrapper; beforeEach(() => { + testProps = { + ...testProps, + defaultView: eventTypes.all, + options: allEvents, + }; wrapper = mount( - + ); }); diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx index 064241a7216f4..4f0a71dcc3ebb 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx @@ -14,7 +14,7 @@ import { EventsByDataset } from '../../../overview/components/events_by_dataset' import { SignalsByCategory } from '../../../overview/components/signals_by_category'; import { Filter, IIndexPattern, Query } from '../../../../../../../src/plugins/data/public'; import { InputsModelId } from '../../store/inputs/constants'; -import { EventType } from '../../../timelines/store/timeline/model'; +import { TimelineEventsType } from '../../../../common/types/timeline'; import { TopNOption } from './helpers'; import * as i18n from './translations'; @@ -45,11 +45,11 @@ const TopNContent = styled.div` export interface Props extends Pick { combinedQueries?: string; - defaultView: EventType; + defaultView: TimelineEventsType; field: string; filters: Filter[]; indexPattern: IIndexPattern; - indexToAdd?: string[] | null; + indexNames: string[]; options: TopNOption[]; query: Query; setAbsoluteRangeDatePicker: ActionCreator<{ @@ -75,7 +75,7 @@ const TopNComponent: React.FC = ({ field, from, indexPattern, - indexToAdd, + indexNames, options, query = DEFAULT_QUERY, setAbsoluteRangeDatePicker, @@ -85,8 +85,10 @@ const TopNComponent: React.FC = ({ to, toggleTopN, }) => { - const [view, setView] = useState(defaultView); - const onViewSelected = useCallback((value: string) => setView(value as EventType), [setView]); + const [view, setView] = useState(defaultView); + const onViewSelected = useCallback((value: string) => setView(value as TimelineEventsType), [ + setView, + ]); useEffect(() => { setView(defaultView); @@ -123,7 +125,7 @@ const TopNComponent: React.FC = ({ from={from} headerChildren={headerChildren} indexPattern={indexPattern} - indexToAdd={indexToAdd} + indexNames={indexNames} onlyField={field} query={query} setAbsoluteRangeDatePickerTarget={setAbsoluteRangeDatePickerTarget} diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/constants.ts b/x-pack/plugins/security_solution/public/common/components/url_state/constants.ts index 5a4aec93dd9aa..e5c09d229808b 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/constants.ts +++ b/x-pack/plugins/security_solution/public/common/components/url_state/constants.ts @@ -17,6 +17,7 @@ export enum CONSTANTS { networkPage = 'network.page', overviewPage = 'overview.page', savedQuery = 'savedQuery', + sourcerer = 'sourcerer', timeline = 'timeline', timelinePage = 'timeline.page', timerange = 'timerange', diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts b/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts index 262c7303dd0a0..05000f91f094c 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts @@ -22,6 +22,8 @@ import { formatDate } from '../super_date_picker'; import { NavTab } from '../navigation/types'; import { CONSTANTS, UrlStateType } from './constants'; import { ReplaceStateInLocation, UpdateUrlStateString } from './types'; +import { sourcererSelectors } from '../../store/sourcerer'; +import { SourcererScopeName, SourcererScopePatterns } from '../../store/sourcerer/model'; export const decodeRisonUrlState = (value: string | undefined): T | null => { try { @@ -116,6 +118,7 @@ export const makeMapStateToProps = () => { const getGlobalFiltersQuerySelector = inputsSelectors.globalFiltersQuerySelector(); const getGlobalSavedQuerySelector = inputsSelectors.globalSavedQuerySelector(); const getTimeline = timelineSelectors.getTimelineByIdSelector(); + const getSourcererScopes = sourcererSelectors.scopesSelector(); const mapStateToProps = (state: State) => { const inputState = getInputsSelector(state); const { linkTo: globalLinkTo, timerange: globalTimerange } = inputState.global; @@ -145,10 +148,16 @@ export const makeMapStateToProps = () => { [CONSTANTS.savedQuery]: savedQuery.id, }; } + const sourcerer = getSourcererScopes(state); + const activeScopes: SourcererScopeName[] = Object.keys(sourcerer) as SourcererScopeName[]; + const selectedPatterns: SourcererScopePatterns = activeScopes + .filter((scope) => scope === SourcererScopeName.default) + .reduce((acc, scope) => ({ ...acc, [scope]: sourcerer[scope]?.selectedPatterns }), {}); return { urlState: { ...searchAttr, + [CONSTANTS.sourcerer]: selectedPatterns, [CONSTANTS.timerange]: { global: { [CONSTANTS.timerange]: globalTimerange, @@ -215,6 +224,17 @@ export const updateUrlStateString = ({ urlStateKey: urlKey, }); } + } else if (urlKey === CONSTANTS.sourcerer) { + const sourcererState = decodeRisonUrlState(newUrlStateString); + if (sourcererState != null && Object.keys(sourcererState).length > 0) { + return replaceStateInLocation({ + history, + pathName, + search, + urlStateToReplace: sourcererState, + urlStateKey: urlKey, + }); + } } else if (urlKey === CONSTANTS.filters) { const queryState = decodeRisonUrlState(newUrlStateString); if (isEmpty(queryState)) { diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx index 72df9d613abac..fc970c066e8a5 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx @@ -161,7 +161,7 @@ describe('UrlStateContainer', () => { ).toEqual({ hash: '', pathname: examplePath, - search: `?query=(language:kuery,query:'host.name:%22siem-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, + search: `?query=(language:kuery,query:'host.name:%22siem-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, state: '', }); } diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/index_mocked.test.tsx b/x-pack/plugins/security_solution/public/common/components/url_state/index_mocked.test.tsx index 723f2d235864f..9e845ec538aa0 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/index_mocked.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/url_state/index_mocked.test.tsx @@ -83,7 +83,7 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () => hash: '', pathname: '/network', search: - "?query=(language:kuery,query:'host.name:%22siem-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "?query=(language:kuery,query:'host.name:%22siem-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", state: '', }); }); @@ -114,7 +114,7 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () => hash: '', pathname: '/network', search: - "?query=(language:kuery,query:'host.name:%22siem-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "?query=(language:kuery,query:'host.name:%22siem-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", state: '', }); }); @@ -147,7 +147,40 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () => hash: '', pathname: '/network', search: - "?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))&timeline=(id:hello_timeline_id,isOpen:!t)", + "?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))&timeline=(id:hello_timeline_id,isOpen:!t)", + state: '', + }); + }); + + test('sourcerer redux state updates the url', () => { + mockProps = getMockPropsObj({ + page: CONSTANTS.networkPage, + examplePath: '/network', + namespaceLower: 'network', + pageName: SecurityPageName.network, + detailName: undefined, + }).noSearch.undefinedQuery; + + const wrapper = mount( + useUrlStateHooks(args)} /> + ); + const newUrlState = { + ...mockProps.urlState, + sourcerer: ['cool', 'patterns'], + }; + + wrapper.setProps({ + hookProps: { ...mockProps, urlState: newUrlState, isInitializing: false }, + }); + wrapper.update(); + + expect( + mockHistory.replace.mock.calls[mockHistory.replace.mock.calls.length - 1][0] + ).toStrictEqual({ + hash: '', + pathname: '/network', + search: + "?sourcerer=!(cool,patterns)&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", state: '', }); }); @@ -176,7 +209,7 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () => hash: '', pathname: examplePath, search: - "?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", state: '', }); } @@ -204,7 +237,7 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () => expect( mockHistory.replace.mock.calls[mockHistory.replace.mock.calls.length - 1][0].search ).toEqual( - "?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))" + "?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))" ); wrapper.setProps({ hookProps: updatedProps }); @@ -213,7 +246,7 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () => expect( mockHistory.replace.mock.calls[mockHistory.replace.mock.calls.length - 1][0].search ).toEqual( - "?query=(language:kuery,query:'host.name:%22siem-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))" + "?query=(language:kuery,query:'host.name:%22siem-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))" ); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx b/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx index 6eccf52ec72da..1e77ae7766630 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx +++ b/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx @@ -8,7 +8,7 @@ import { get, isEmpty } from 'lodash/fp'; import { Dispatch } from 'redux'; import { Query, Filter } from '../../../../../../../src/plugins/data/public'; -import { inputsActions } from '../../store/actions'; +import { inputsActions, sourcererActions } from '../../store/actions'; import { InputsModelId, TimeRangeKinds } from '../../store/inputs/constants'; import { UrlInputsModel, @@ -22,6 +22,8 @@ import { decodeRisonUrlState } from './helpers'; import { normalizeTimeRange } from './normalize_time_range'; import { DispatchSetInitialStateFromUrl, SetInitialStateFromUrl } from './types'; import { queryTimelineById } from '../../../timelines/components/open_timeline/helpers'; +import { SourcererScopeName, SourcererScopePatterns } from '../../store/sourcerer/model'; +import { SecurityPageName } from '../../../../common/constants'; export const dispatchSetInitialStateFromUrl = ( dispatch: Dispatch @@ -40,6 +42,22 @@ export const dispatchSetInitialStateFromUrl = ( if (urlKey === CONSTANTS.timerange) { updateTimerange(newUrlStateString, dispatch); } + if (urlKey === CONSTANTS.sourcerer) { + const sourcererState = decodeRisonUrlState(newUrlStateString); + if (sourcererState != null) { + const activeScopes: SourcererScopeName[] = Object.keys(sourcererState).filter( + (key) => !(key === SourcererScopeName.default && pageName === SecurityPageName.detections) + ) as SourcererScopeName[]; + activeScopes.forEach((scope) => + dispatch( + sourcererActions.setSelectedIndexPatterns({ + id: scope, + selectedPatterns: sourcererState[scope] ?? [], + }) + ) + ); + } + } if (urlKey === CONSTANTS.appQuery && indexPattern != null) { const appQuery = decodeRisonUrlState(newUrlStateString); diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts b/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts index 8d471e843320c..6f04226fa3a19 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts +++ b/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts @@ -117,6 +117,7 @@ export const defaultProps: UrlStateContainerPropTypes = { id: '', isOpen: false, }, + [CONSTANTS.sourcerer]: {}, }, setInitialStateFromUrl: dispatchSetInitialStateFromUrl(mockDispatch), updateTimeline: (jest.fn() as unknown) as DispatchUpdateTimeline, diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/types.ts b/x-pack/plugins/security_solution/public/common/components/url_state/types.ts index f383e18132385..301771a4db6b9 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/url_state/types.ts @@ -22,11 +22,13 @@ import { DispatchUpdateTimeline } from '../../../timelines/components/open_timel import { NavTab } from '../navigation/types'; import { CONSTANTS, UrlStateType } from './constants'; +import { SourcererScopePatterns } from '../../store/sourcerer/model'; export const ALL_URL_STATE_KEYS: KeyUrlState[] = [ CONSTANTS.appQuery, CONSTANTS.filters, CONSTANTS.savedQuery, + CONSTANTS.sourcerer, CONSTANTS.timerange, CONSTANTS.timeline, ]; @@ -36,6 +38,7 @@ export const URL_STATE_KEYS: Record = { CONSTANTS.appQuery, CONSTANTS.filters, CONSTANTS.savedQuery, + CONSTANTS.sourcerer, CONSTANTS.timerange, CONSTANTS.timeline, ], @@ -43,6 +46,7 @@ export const URL_STATE_KEYS: Record = { CONSTANTS.appQuery, CONSTANTS.filters, CONSTANTS.savedQuery, + CONSTANTS.sourcerer, CONSTANTS.timerange, CONSTANTS.timeline, ], @@ -51,6 +55,7 @@ export const URL_STATE_KEYS: Record = { CONSTANTS.appQuery, CONSTANTS.filters, CONSTANTS.savedQuery, + CONSTANTS.sourcerer, CONSTANTS.timerange, CONSTANTS.timeline, ], @@ -58,6 +63,7 @@ export const URL_STATE_KEYS: Record = { CONSTANTS.appQuery, CONSTANTS.filters, CONSTANTS.savedQuery, + CONSTANTS.sourcerer, CONSTANTS.timerange, CONSTANTS.timeline, ], @@ -65,6 +71,7 @@ export const URL_STATE_KEYS: Record = { CONSTANTS.appQuery, CONSTANTS.filters, CONSTANTS.savedQuery, + CONSTANTS.sourcerer, CONSTANTS.timerange, CONSTANTS.timeline, ], @@ -72,6 +79,7 @@ export const URL_STATE_KEYS: Record = { CONSTANTS.appQuery, CONSTANTS.filters, CONSTANTS.savedQuery, + CONSTANTS.sourcerer, CONSTANTS.timerange, CONSTANTS.timeline, ], @@ -93,6 +101,7 @@ export interface UrlState { [CONSTANTS.appQuery]?: Query; [CONSTANTS.filters]?: Filter[]; [CONSTANTS.savedQuery]?: string; + [CONSTANTS.sourcerer]: SourcererScopePatterns; [CONSTANTS.timerange]: UrlInputsModel; [CONSTANTS.timeline]: TimelineUrl; } diff --git a/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/index.tsx b/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/index.tsx index f6ebbb990f223..489ccb23c9b2c 100644 --- a/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/index.tsx @@ -29,6 +29,7 @@ const AnomaliesQueryTabBodyComponent: React.FC = ({ AnomaliesTableComponent, flowTarget, ip, + indexNames, }) => { const { jobs } = useInstalledSecurityJobs(); const [anomalyScore] = useUiSetting$(DEFAULT_ANOMALY_SCORE); @@ -57,6 +58,7 @@ const AnomaliesQueryTabBodyComponent: React.FC = ({ endDate={endDate} filterQuery={mergedFilterQuery} id={ID} + indexNames={indexNames} setQuery={setQuery} startDate={startDate} {...histogramConfigs} diff --git a/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/types.ts b/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/types.ts index d716df70246f7..3ce4b8b6d4494 100644 --- a/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/types.ts +++ b/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/types.ts @@ -24,6 +24,7 @@ export type AnomaliesQueryTabBodyProps = QueryTabBodyProps & { deleteQuery?: ({ id }: { id: string }) => void; endDate: GlobalTimeArgs['to']; flowTarget?: FlowTarget; + indexNames: string[]; narrowDateRange: NarrowDateRange; setQuery: GlobalTimeArgs['setQuery']; startDate: GlobalTimeArgs['from']; diff --git a/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts b/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts index d70762615818b..dc2d6605bc292 100644 --- a/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts +++ b/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts @@ -8,7 +8,6 @@ import deepEqual from 'fast-deep-equal'; import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; -import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; import { inputsModel } from '../../../../common/store'; import { useKibana } from '../../../../common/lib/kibana'; import { @@ -18,11 +17,15 @@ import { LastTimeDetails, LastEventIndexKey, } from '../../../../../common/search_strategy/timeline'; -import { AbortError } from '../../../../../../../../src/plugins/data/common'; -import { useWithSource } from '../../source'; +import { + AbortError, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../../src/plugins/data/common'; import * as i18n from './translations'; +import { DocValueFields } from '../../../../../common/search_strategy'; -// const ID = 'timelineEventsLastEventTimeQuery'; +const ID = 'timelineEventsLastEventTimeQuery'; export interface UseTimelineLastEventTimeArgs { lastSeen: string | null; @@ -31,26 +34,29 @@ export interface UseTimelineLastEventTimeArgs { } interface UseTimelineLastEventTimeProps { + docValueFields: DocValueFields[]; indexKey: LastEventIndexKey; + indexNames: string[]; details: LastTimeDetails; } export const useTimelineLastEventTime = ({ + docValueFields, indexKey, + indexNames, details, }: UseTimelineLastEventTimeProps): [boolean, UseTimelineLastEventTimeArgs] => { - const { data, notifications, uiSettings } = useKibana().services; - const { docValueFields } = useWithSource('default'); + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); const [loading, setLoading] = useState(false); const [TimelineLastEventTimeRequest, setTimelineLastEventTimeRequest] = useState< TimelineEventsLastEventTimeRequestOptions >({ - defaultIndex, - factoryQueryType: TimelineEventsQueries.lastEventTime, + defaultIndex: indexNames, docValueFields, + factoryQueryType: TimelineEventsQueries.lastEventTime, + id: ID, indexKey, details, }); @@ -80,7 +86,7 @@ export const useTimelineLastEventTime = ({ }) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setTimelineLastEventTimeResponse((prevResponse) => ({ @@ -91,7 +97,7 @@ export const useTimelineLastEventTime = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -129,7 +135,8 @@ export const useTimelineLastEventTime = ({ setTimelineLastEventTimeRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, + docValueFields, indexKey, details, }; @@ -138,7 +145,7 @@ export const useTimelineLastEventTime = ({ } return prevRequest; }); - }, [defaultIndex, details, indexKey]); + }, [indexNames, details, docValueFields, indexKey]); useEffect(() => { timelineLastEventTimeSearch(TimelineLastEventTimeRequest); diff --git a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts index 65ad3cc994c67..ca8bcc637717b 100644 --- a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts +++ b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts @@ -5,21 +5,24 @@ */ import deepEqual from 'fast-deep-equal'; -import { isEmpty, noop } from 'lodash/fp'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { noop } from 'lodash/fp'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { MatrixHistogramQueryProps } from '../../components/matrix_histogram/types'; -import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import { inputsModel } from '../../../common/store'; import { createFilter } from '../../../common/containers/helpers'; -import { useKibana, useUiSetting$ } from '../../../common/lib/kibana'; +import { useKibana } from '../../../common/lib/kibana'; import { MatrixHistogramQuery, MatrixHistogramRequestOptions, MatrixHistogramStrategyResponse, MatrixHistogramData, } from '../../../../common/search_strategy/security_solution'; -import { AbortError } from '../../../../../../../src/plugins/data/common'; +import { + AbortError, + isErrorResponse, + isCompleteResponse, +} from '../../../../../../../src/plugins/data/common'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; import * as i18n from './translations'; @@ -36,25 +39,18 @@ export const useMatrixHistogram = ({ errorMessage, filterQuery, histogramType, - indexToAdd, + indexNames, stackByField, startDate, }: MatrixHistogramQueryProps): [boolean, UseMatrixHistogramArgs] => { const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const [configIndex] = useUiSetting$(DEFAULT_INDEX_KEY); - const defaultIndex = useMemo(() => { - if (indexToAdd != null && !isEmpty(indexToAdd)) { - return [...configIndex, ...indexToAdd]; - } - return configIndex; - }, [configIndex, indexToAdd]); const [loading, setLoading] = useState(false); const [matrixHistogramRequest, setMatrixHistogramRequest] = useState< MatrixHistogramRequestOptions >({ - defaultIndex, + defaultIndex: indexNames, factoryQueryType: MatrixHistogramQuery, filterQuery: createFilter(filterQuery), histogramType, @@ -90,7 +86,7 @@ export const useMatrixHistogram = ({ }) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setMatrixHistogramResponse((prevResponse) => ({ @@ -102,7 +98,7 @@ export const useMatrixHistogram = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -136,7 +132,7 @@ export const useMatrixHistogram = ({ setMatrixHistogramRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, filterQuery: createFilter(filterQuery), timerange: { interval: '12h', @@ -149,7 +145,7 @@ export const useMatrixHistogram = ({ } return prevRequest; }); - }, [defaultIndex, endDate, filterQuery, startDate]); + }, [indexNames, endDate, filterQuery, startDate]); useEffect(() => { hostsSearch(matrixHistogramRequest); diff --git a/x-pack/plugins/security_solution/public/common/containers/query_template.tsx b/x-pack/plugins/security_solution/public/common/containers/query_template.tsx index eaa43c255a944..80791d91481a8 100644 --- a/x-pack/plugins/security_solution/public/common/containers/query_template.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/query_template.tsx @@ -14,6 +14,7 @@ import { DocValueFields } from './source'; export { DocValueFields }; export interface QueryTemplateProps { + indexNames: string[]; docValueFields?: DocValueFields[]; id?: string; endDate?: string; diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.gql_query.ts b/x-pack/plugins/security_solution/public/common/containers/source/index.gql_query.ts deleted file mode 100644 index 630515c5cbed4..0000000000000 --- a/x-pack/plugins/security_solution/public/common/containers/source/index.gql_query.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import gql from 'graphql-tag'; - -export const sourceQuery = gql` - query SourceQuery($sourceId: ID = "default", $defaultIndex: [String!]!) { - source(id: $sourceId) { - id - status { - indicesExist(defaultIndex: $defaultIndex) - indexFields(defaultIndex: $defaultIndex) { - category - description - example - indexes - name - searchable - type - aggregatable - format - esTypes - subType - } - } - } - } -`; diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx b/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx deleted file mode 100644 index 8ba7f7da7b8e3..0000000000000 --- a/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { act, renderHook } from '@testing-library/react-hooks'; - -import { useWithSource, indicesExistOrDataTemporarilyUnavailable } from '.'; -import { NO_ALERT_INDEX } from '../../../../common/constants'; -import { mockBrowserFields, mockIndexFields, mocksSource } from './mock'; - -jest.mock('../../lib/kibana'); -jest.mock('../../utils/apollo_context', () => ({ - useApolloClient: jest.fn().mockReturnValue({ - query: jest.fn().mockImplementation(() => Promise.resolve(mocksSource[0].result)), - }), -})); - -describe('Index Fields & Browser Fields', () => { - test('At initialization the value of indicesExists should be true', async () => { - const { result, waitForNextUpdate } = renderHook(() => useWithSource()); - const initialResult = result.current; - - await waitForNextUpdate(); - - return expect(initialResult).toEqual({ - browserFields: {}, - docValueFields: [], - errorMessage: null, - indexPattern: { - fields: [], - title: - 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*', - }, - indicesExist: true, - loading: true, - }); - }); - - test('returns memoized value', async () => { - const { result, waitForNextUpdate, rerender } = renderHook(() => useWithSource()); - await waitForNextUpdate(); - - const result1 = result.current; - act(() => rerender()); - const result2 = result.current; - - return expect(result1).toBe(result2); - }); - - test('Index Fields', async () => { - const { result, waitForNextUpdate } = renderHook(() => useWithSource()); - - await waitForNextUpdate(); - - return expect(result).toEqual({ - current: { - indicesExist: true, - browserFields: mockBrowserFields, - docValueFields: [ - { - field: '@timestamp', - format: 'date_time', - }, - { - field: 'event.end', - format: 'date_time', - }, - ], - indexPattern: { - fields: mockIndexFields, - title: - 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*', - }, - loading: false, - errorMessage: null, - }, - error: undefined, - }); - }); - - test('Make sure we are not querying for NO_ALERT_INDEX and it is not includes in the index pattern', async () => { - const { result, waitForNextUpdate } = renderHook(() => - useWithSource('default', [NO_ALERT_INDEX]) - ); - - await waitForNextUpdate(); - return expect(result.current.indexPattern.title).toEqual( - 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*' - ); - }); - - describe('indicesExistOrDataTemporarilyUnavailable', () => { - test('it returns true when undefined', () => { - let undefVar; - const result = indicesExistOrDataTemporarilyUnavailable(undefVar); - expect(result).toBeTruthy(); - }); - test('it returns true when true', () => { - const result = indicesExistOrDataTemporarilyUnavailable(true); - expect(result).toBeTruthy(); - }); - test('it returns false when false', () => { - const result = indicesExistOrDataTemporarilyUnavailable(false); - expect(result).toBeFalsy(); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx index ffbecf9e3d433..4b1db8a2871bd 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx @@ -4,42 +4,30 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isUndefined } from 'lodash'; import { set } from '@elastic/safer-lodash-set/fp'; -import { get, keyBy, pick, isEmpty } from 'lodash/fp'; -import { useEffect, useMemo, useState } from 'react'; +import { keyBy, pick, isEmpty, isEqual, isUndefined } from 'lodash/fp'; import memoizeOne from 'memoize-one'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useDispatch, useSelector, shallowEqual } from 'react-redux'; import { IIndexPattern } from 'src/plugins/data/public'; -import { DEFAULT_INDEX_KEY, NO_ALERT_INDEX } from '../../../../common/constants'; -import { useUiSetting$ } from '../../lib/kibana'; +import { useKibana } from '../../lib/kibana'; +import { + IndexField, + IndexFieldsStrategyResponse, + IndexFieldsStrategyRequest, + BrowserField, + BrowserFields, +} from '../../../../common/search_strategy/index_fields'; +import { AbortError } from '../../../../../../../src/plugins/data/common'; +import * as i18n from './translations'; +import { SourcererScopeName } from '../../store/sourcerer/model'; +import { sourcererActions, sourcererSelectors } from '../../store/sourcerer'; -import { IndexField, SourceQuery } from '../../../graphql/types'; +import { State } from '../../store'; +import { DocValueFields } from '../../../../common/search_strategy/common'; -import { sourceQuery } from './index.gql_query'; -import { useApolloClient } from '../../utils/apollo_context'; - -export { sourceQuery }; - -export interface BrowserField { - aggregatable: boolean; - category: string; - description: string | null; - example: string | number | null; - fields: Readonly>>; - format: string; - indexes: string[]; - name: string; - searchable: boolean; - type: string; -} - -export interface DocValueFields { - field: string; - format: string; -} - -export type BrowserFields = Readonly>>; +export { BrowserField, BrowserFields, DocValueFields }; export const getAllBrowserFields = (browserFields: BrowserFields): Array> => Object.values(browserFields).reduce>>( @@ -85,14 +73,12 @@ export const getDocValueFields = memoizeOne( (_title: string, fields: IndexField[]): DocValueFields[] => fields && fields.length > 0 ? fields.reduce((accumulator: DocValueFields[], field: IndexField) => { - if (field.type === 'date' && accumulator.length < 100) { - const format: string = - field.format != null && !isEmpty(field.format) ? field.format : 'date_time'; + if (field.readFromDocValues && accumulator.length < 100) { return [ ...accumulator, { field: field.name, - format, + format: field.format, }, ]; } @@ -107,115 +93,196 @@ export const indicesExistOrDataTemporarilyUnavailable = ( indicesExist: boolean | null | undefined ) => indicesExist || isUndefined(indicesExist); -const EMPTY_BROWSER_FIELDS = {}; -const EMPTY_DOCVALUE_FIELD: DocValueFields[] = []; +const DEFAULT_BROWSER_FIELDS = {}; +const DEFAULT_INDEX_PATTERNS = { fields: [], title: '' }; +const DEFAULT_DOC_VALUE_FIELDS: DocValueFields[] = []; -interface UseWithSourceState { +interface FetchIndexReturn { browserFields: BrowserFields; docValueFields: DocValueFields[]; - errorMessage: string | null; - indexPattern: IIndexPattern; - indicesExist: boolean | undefined | null; - loading: boolean; + indexes: string[]; + indexExists: boolean; + indexPatterns: IIndexPattern; } -export const useWithSource = ( - sourceId = 'default', - indexToAdd?: string[] | null, - onlyCheckIndexToAdd?: boolean, - // Fun fact: When using this hook multiple times within a component (e.g. add_exception_modal & edit_exception_modal), - // the apolloClient will perform queryDeduplication and prevent the first query from executing. A deep compare is not - // performed on `indices`, so another field must be passed to circumvent this. - // For details, see https://github.com/apollographql/react-apollo/issues/2202 - queryDeduplication = 'default' -) => { - const [configIndex] = useUiSetting$(DEFAULT_INDEX_KEY); - const defaultIndex = useMemo(() => { - const filterIndexAdd = (indexToAdd ?? []).filter((item) => item !== NO_ALERT_INDEX); - if (!isEmpty(filterIndexAdd)) { - return onlyCheckIndexToAdd ? filterIndexAdd : [...configIndex, ...filterIndexAdd]; - } - return configIndex; - }, [configIndex, indexToAdd, onlyCheckIndexToAdd]); - - const [state, setState] = useState({ - browserFields: EMPTY_BROWSER_FIELDS, - docValueFields: EMPTY_DOCVALUE_FIELD, - errorMessage: null, - indexPattern: getIndexFields(defaultIndex.join(), []), - indicesExist: indicesExistOrDataTemporarilyUnavailable(undefined), - loading: true, +export const useFetchIndex = ( + indexNames: string[], + onlyCheckIfIndicesExist: boolean = false +): [boolean, FetchIndexReturn] => { + const { data, notifications } = useKibana().services; + const abortCtrl = useRef(new AbortController()); + const previousIndexesName = useRef([]); + const [isLoading, setLoading] = useState(true); + + const [state, setState] = useState({ + browserFields: DEFAULT_BROWSER_FIELDS, + docValueFields: DEFAULT_DOC_VALUE_FIELDS, + indexes: indexNames, + indexExists: true, + indexPatterns: DEFAULT_INDEX_PATTERNS, }); - const apolloClient = useApolloClient(); + const indexFieldsSearch = useCallback( + (iNames) => { + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + const searchSubscription$ = data.search + .search( + { indices: iNames, onlyCheckIfIndicesExist }, + { + abortSignal: abortCtrl.current.signal, + strategy: 'securitySolutionIndexFields', + } + ) + .subscribe({ + next: (response) => { + if (!response.isPartial && !response.isRunning) { + if (!didCancel) { + const stringifyIndices = response.indicesExist.sort().join(); + previousIndexesName.current = response.indicesExist; + setLoading(false); + setState({ + browserFields: getBrowserFields(stringifyIndices, response.indexFields), + docValueFields: getDocValueFields(stringifyIndices, response.indexFields), + indexes: response.indicesExist, + indexExists: response.indicesExist.length > 0, + indexPatterns: getIndexFields(stringifyIndices, response.indexFields), + }); + } + searchSubscription$.unsubscribe(); + } else if (!didCancel && response.isPartial && !response.isRunning) { + setLoading(false); + notifications.toasts.addWarning(i18n.ERROR_BEAT_FIELDS); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!didCancel) { + setLoading(false); + } - useEffect(() => { - let isSubscribed = true; - const abortCtrl = new AbortController(); - - async function fetchSource() { - if (!apolloClient) return; - - setState((prevState) => ({ ...prevState, loading: true })); - - try { - const result = await apolloClient.query< - SourceQuery.Query, - SourceQuery.Variables & { queryDeduplication: string } - >({ - query: sourceQuery, - fetchPolicy: 'cache-first', - variables: { - sourceId, - defaultIndex, - queryDeduplication, - }, - context: { - fetchOptions: { - signal: abortCtrl.signal, + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ + text: msg.message, + title: i18n.FAIL_BEAT_FIELDS, + }); + } }, - }, - }); - - if (isSubscribed) { - setState({ - loading: false, - indicesExist: indicesExistOrDataTemporarilyUnavailable( - get('data.source.status.indicesExist', result) - ), - browserFields: getBrowserFields( - defaultIndex.join(), - get('data.source.status.indexFields', result) - ), - docValueFields: getDocValueFields( - defaultIndex.join(), - get('data.source.status.indexFields', result) - ), - indexPattern: getIndexFields( - defaultIndex.join(), - get('data.source.status.indexFields', result) - ), - errorMessage: null, }); - } - } catch (error) { - if (isSubscribed) { - setState((prevState) => ({ - ...prevState, - loading: false, - errorMessage: error.message, - })); - } - } + }; + abortCtrl.current.abort(); + asyncSearch(); + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts, onlyCheckIfIndicesExist] + ); + + useEffect(() => { + if (!isEmpty(indexNames) && !isEqual(previousIndexesName.current, indexNames)) { + indexFieldsSearch(indexNames); } + }, [indexNames, indexFieldsSearch, previousIndexesName]); + + return [isLoading, state]; +}; + +export const useIndexFields = (sourcererScopeName: SourcererScopeName) => { + const { data, notifications } = useKibana().services; + const abortCtrl = useRef(new AbortController()); + const dispatch = useDispatch(); + const previousIndexesName = useRef([]); + + const indexNamesSelectedSelector = useMemo( + () => sourcererSelectors.getIndexNamesSelectedSelector(), + [] + ); + const indexNames = useSelector( + (state) => indexNamesSelectedSelector(state, sourcererScopeName), + shallowEqual + ); - fetchSource(); + const setLoading = useCallback( + (loading: boolean) => { + dispatch(sourcererActions.setSourcererScopeLoading({ id: sourcererScopeName, loading })); + }, + [dispatch, sourcererScopeName] + ); + + const indexFieldsSearch = useCallback( + (indicesName) => { + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + const searchSubscription$ = data.search + .search( + { indices: indicesName, onlyCheckIfIndicesExist: false }, + { + abortSignal: abortCtrl.current.signal, + strategy: 'securitySolutionIndexFields', + } + ) + .subscribe({ + next: (response) => { + if (!response.isPartial && !response.isRunning) { + if (!didCancel) { + const stringifyIndices = response.indicesExist.sort().join(); + previousIndexesName.current = response.indicesExist; + dispatch( + sourcererActions.setSource({ + id: sourcererScopeName, + payload: { + browserFields: getBrowserFields(stringifyIndices, response.indexFields), + docValueFields: getDocValueFields(stringifyIndices, response.indexFields), + errorMessage: null, + id: sourcererScopeName, + indexPattern: getIndexFields(stringifyIndices, response.indexFields), + indicesExist: response.indicesExist.length > 0, + loading: false, + }, + }) + ); + } + searchSubscription$.unsubscribe(); + } else if (!didCancel && response.isPartial && !response.isRunning) { + // TODO: Make response error status clearer + setLoading(false); + notifications.toasts.addWarning(i18n.ERROR_BEAT_FIELDS); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!didCancel) { + setLoading(false); + } - return () => { - isSubscribed = false; - return abortCtrl.abort(); - }; - }, [apolloClient, sourceId, defaultIndex, queryDeduplication]); + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ + text: msg.message, + title: i18n.FAIL_BEAT_FIELDS, + }); + } + }, + }); + }; + abortCtrl.current.abort(); + asyncSearch(); + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, dispatch, notifications.toasts, setLoading, sourcererScopeName] + ); - return state; + useEffect(() => { + if (!isEmpty(indexNames) && !isEqual(previousIndexesName.current, indexNames)) { + indexFieldsSearch(indexNames); + } + }, [indexNames, indexFieldsSearch, previousIndexesName]); }; diff --git a/x-pack/plugins/security_solution/public/common/containers/source/mock.ts b/x-pack/plugins/security_solution/public/common/containers/source/mock.ts index bba6a15d73970..7fcd11f71f081 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/mock.ts +++ b/x-pack/plugins/security_solution/public/common/containers/source/mock.ts @@ -5,347 +5,296 @@ */ import { DEFAULT_INDEX_PATTERN } from '../../../../common/constants'; +import { DocValueFields } from '../../../../common/search_strategy'; +import { BrowserFields } from '../../../../common/search_strategy/index_fields'; -import { BrowserFields, DocValueFields } from '.'; -import { sourceQuery } from './index.gql_query'; - -export const mocksSource = [ - { - request: { - query: sourceQuery, - variables: { - sourceId: 'default', - defaultIndex: DEFAULT_INDEX_PATTERN, - }, +export const mocksSource = { + indexFields: [ + { + category: 'base', + description: + 'Date/time when the event originated. For log events this is the date/time when the event was generated, and not when it was read. Required field for all events.', + example: '2016-05-23T08:05:34.853Z', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: '@timestamp', + searchable: true, + type: 'date', + aggregatable: true, }, - result: { - data: { - source: { - id: 'default', - configuration: {}, - status: { - indicesExist: true, - winlogbeatIndices: [ - 'winlogbeat-7.0.0-2019.02.17', - 'winlogbeat-7.0.0-2019.02.18', - 'winlogbeat-7.0.0-2019.02.19', - 'winlogbeat-7.0.0-2019.02.20', - 'winlogbeat-7.0.0-2019.02.21', - 'winlogbeat-7.0.0-2019.02.21-000001', - 'winlogbeat-7.0.0-2019.02.22', - 'winlogbeat-8.0.0-2019.02.19-000001', - ], - auditbeatIndices: [ - 'auditbeat-7.0.0-2019.02.17', - 'auditbeat-7.0.0-2019.02.18', - 'auditbeat-7.0.0-2019.02.19', - 'auditbeat-7.0.0-2019.02.20', - 'auditbeat-7.0.0-2019.02.21', - 'auditbeat-7.0.0-2019.02.21-000001', - 'auditbeat-7.0.0-2019.02.22', - 'auditbeat-8.0.0-2019.02.19-000001', - ], - filebeatIndices: [ - 'filebeat-7.0.0-iot-2019.06', - 'filebeat-7.0.0-iot-2019.07', - 'filebeat-7.0.0-iot-2019.08', - 'filebeat-7.0.0-iot-2019.09', - 'filebeat-7.0.0-iot-2019.10', - 'filebeat-8.0.0-2019.02.19-000001', - ], - indexFields: [ - { - category: 'base', - description: - 'Date/time when the event originated. For log events this is the date/time when the event was generated, and not when it was read. Required field for all events.', - example: '2016-05-23T08:05:34.853Z', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: '@timestamp', - searchable: true, - type: 'date', - aggregatable: true, - }, - { - category: 'agent', - description: - 'Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but `agent.id` does not.', - example: '8a4f500f', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'agent.ephemeral_id', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'agent', - description: null, - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'agent.hostname', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'agent', - description: - 'Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.', - example: '8a4f500d', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'agent.id', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'agent', - description: - 'Name of the agent. This is a name that can be given to an agent. This can be helpful if for example two Filebeat instances are running on the same host but a human readable separation is needed on which Filebeat instance data is coming from. If no name is given, the name is often left empty.', - example: 'foo', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'agent.name', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'auditd', - description: null, - example: null, - format: '', - indexes: ['auditbeat'], - name: 'auditd.data.a0', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'auditd', - description: null, - example: null, - format: '', - indexes: ['auditbeat'], - name: 'auditd.data.a1', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'auditd', - description: null, - example: null, - format: '', - indexes: ['auditbeat'], - name: 'auditd.data.a2', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'client', - description: - 'Some event client addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the `.address` field. Then it should be duplicated to `.ip` or `.domain`, depending on which one it is.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'client.address', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'client', - description: 'Bytes sent from the client to the server.', - example: '184', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'client.bytes', - searchable: true, - type: 'number', - aggregatable: true, - }, - { - category: 'client', - description: 'Client domain.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'client.domain', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'client', - description: 'Country ISO code.', - example: 'CA', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'client.geo.country_iso_code', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'cloud', - description: - 'The cloud account or organization id used to identify different entities in a multi-tenant environment. Examples: AWS account id, Google Cloud ORG Id, or other unique identifier.', - example: '666777888999', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'cloud.account.id', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'cloud', - description: 'Availability zone in which this host is running.', - example: 'us-east-1c', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'cloud.availability_zone', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'container', - description: 'Unique container id.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'container.id', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'container', - description: 'Name of the image the container was built on.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'container.image.name', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'container', - description: 'Container image tag.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'container.image.tag', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'destination', - description: - 'Some event destination addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the `.address` field. Then it should be duplicated to `.ip` or `.domain`, depending on which one it is.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'destination.address', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - category: 'destination', - description: 'Bytes sent from the destination to the source.', - example: '184', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'destination.bytes', - searchable: true, - type: 'number', - aggregatable: true, - }, - { - category: 'destination', - description: 'Destination domain.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'destination.domain', - searchable: true, - type: 'string', - aggregatable: true, - }, - { - aggregatable: true, - category: 'destination', - description: - 'IP address of the destination. Can be one or multiple IPv4 or IPv6 addresses.', - example: '', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'destination.ip', - searchable: true, - type: 'ip', - }, - { - aggregatable: true, - category: 'destination', - description: 'Port of the destination.', - example: '', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'destination.port', - searchable: true, - type: 'long', - }, - { - aggregatable: true, - category: 'source', - description: - 'IP address of the source. Can be one or multiple IPv4 or IPv6 addresses.', - example: '', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'source.ip', - searchable: true, - type: 'ip', - }, - { - aggregatable: true, - category: 'source', - description: 'Port of the source.', - example: '', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'source.port', - searchable: true, - type: 'long', - }, - { - aggregatable: true, - category: 'event', - description: - 'event.end contains the date when the event ended or when the activity was last observed.', - example: null, - format: '', - indexes: DEFAULT_INDEX_PATTERN, - name: 'event.end', - searchable: true, - type: 'date', - }, - ], - }, - }, - }, + { + category: 'agent', + description: + 'Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but `agent.id` does not.', + example: '8a4f500f', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'agent.ephemeral_id', + searchable: true, + type: 'string', + aggregatable: true, }, - }, -]; + { + category: 'agent', + description: null, + example: null, + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'agent.hostname', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'agent', + description: + 'Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.', + example: '8a4f500d', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'agent.id', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'agent', + description: + 'Name of the agent. This is a name that can be given to an agent. This can be helpful if for example two Filebeat instances are running on the same host but a human readable separation is needed on which Filebeat instance data is coming from. If no name is given, the name is often left empty.', + example: 'foo', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'agent.name', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'auditd', + description: null, + example: null, + format: '', + indexes: ['auditbeat'], + name: 'auditd.data.a0', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'auditd', + description: null, + example: null, + format: '', + indexes: ['auditbeat'], + name: 'auditd.data.a1', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'auditd', + description: null, + example: null, + format: '', + indexes: ['auditbeat'], + name: 'auditd.data.a2', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'client', + description: + 'Some event client addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the `.address` field. Then it should be duplicated to `.ip` or `.domain`, depending on which one it is.', + example: null, + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'client.address', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'client', + description: 'Bytes sent from the client to the server.', + example: '184', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'client.bytes', + searchable: true, + type: 'number', + aggregatable: true, + }, + { + category: 'client', + description: 'Client domain.', + example: null, + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'client.domain', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'client', + description: 'Country ISO code.', + example: 'CA', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'client.geo.country_iso_code', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'cloud', + description: + 'The cloud account or organization id used to identify different entities in a multi-tenant environment. Examples: AWS account id, Google Cloud ORG Id, or other unique identifier.', + example: '666777888999', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'cloud.account.id', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'cloud', + description: 'Availability zone in which this host is running.', + example: 'us-east-1c', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'cloud.availability_zone', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'container', + description: 'Unique container id.', + example: null, + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'container.id', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'container', + description: 'Name of the image the container was built on.', + example: null, + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'container.image.name', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'container', + description: 'Container image tag.', + example: null, + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'container.image.tag', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'destination', + description: + 'Some event destination addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the `.address` field. Then it should be duplicated to `.ip` or `.domain`, depending on which one it is.', + example: null, + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'destination.address', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + category: 'destination', + description: 'Bytes sent from the destination to the source.', + example: '184', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'destination.bytes', + searchable: true, + type: 'number', + aggregatable: true, + }, + { + category: 'destination', + description: 'Destination domain.', + example: null, + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'destination.domain', + searchable: true, + type: 'string', + aggregatable: true, + }, + { + aggregatable: true, + category: 'destination', + description: 'IP address of the destination. Can be one or multiple IPv4 or IPv6 addresses.', + example: '', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'destination.ip', + searchable: true, + type: 'ip', + }, + { + aggregatable: true, + category: 'destination', + description: 'Port of the destination.', + example: '', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'destination.port', + searchable: true, + type: 'long', + }, + { + aggregatable: true, + category: 'source', + description: 'IP address of the source. Can be one or multiple IPv4 or IPv6 addresses.', + example: '', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'source.ip', + searchable: true, + type: 'ip', + }, + { + aggregatable: true, + category: 'source', + description: 'Port of the source.', + example: '', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'source.port', + searchable: true, + type: 'long', + }, + { + aggregatable: true, + category: 'event', + description: + 'event.end contains the date when the event ended or when the activity was last observed.', + example: null, + format: '', + indexes: DEFAULT_INDEX_PATTERN, + name: 'event.end', + searchable: true, + type: 'date', + }, + ], +}; export const mockIndexFields = [ { aggregatable: true, name: '@timestamp', searchable: true, type: 'date' }, diff --git a/x-pack/plugins/security_solution/public/common/containers/source/translations.ts b/x-pack/plugins/security_solution/public/common/containers/source/translations.ts new file mode 100644 index 0000000000000..f12a9a0b41a7b --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/containers/source/translations.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ERROR_BEAT_FIELDS = i18n.translate( + 'xpack.securitySolution.beatFields.errorSearchDescription', + { + defaultMessage: `An error has occurred on getting beat fields`, + } +); + +export const FAIL_BEAT_FIELDS = i18n.translate( + 'xpack.securitySolution.beatFields.failSearchDescription', + { + defaultMessage: `Failed to run search on beat fields`, + } +); diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/constants.ts b/x-pack/plugins/security_solution/public/common/containers/sourcerer/constants.ts index 106294ba54f5a..be3d074811032 100644 --- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/constants.ts +++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/constants.ts @@ -4,26 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export const SOURCERER_FEATURE_FLAG_ON = false; - -export enum SecurityPageName { - default = 'default', - host = 'host', - detections = 'detections', - timeline = 'timeline', - network = 'network', -} - -export type SourceGroupsType = keyof typeof SecurityPageName; - -export const sourceGroups = { - [SecurityPageName.default]: [ - 'apm-*-transaction*', - 'auditbeat-*', - 'endgame-*', - 'filebeat-*', - 'logs-*', - 'winlogbeat-*', - 'blobbeat-*', - ], -}; +export const SOURCERER_FEATURE_FLAG_ON = true; diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/format.test.tsx b/x-pack/plugins/security_solution/public/common/containers/sourcerer/format.test.tsx deleted file mode 100644 index b8017df09b738..0000000000000 --- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/format.test.tsx +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { indicesExistOrDataTemporarilyUnavailable } from './format'; - -describe('indicesExistOrDataTemporarilyUnavailable', () => { - it('it returns true when undefined', () => { - let undefVar; - const result = indicesExistOrDataTemporarilyUnavailable(undefVar); - expect(result).toBeTruthy(); - }); - it('it returns true when true', () => { - const result = indicesExistOrDataTemporarilyUnavailable(true); - expect(result).toBeTruthy(); - }); - it('it returns false when false', () => { - const result = indicesExistOrDataTemporarilyUnavailable(false); - expect(result).toBeFalsy(); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/format.ts b/x-pack/plugins/security_solution/public/common/containers/sourcerer/format.ts deleted file mode 100644 index 8c9a16ed705ef..0000000000000 --- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/format.ts +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { isEmpty, pick } from 'lodash/fp'; -import memoizeOne from 'memoize-one'; -import { set } from '@elastic/safer-lodash-set/fp'; -import { isUndefined } from 'lodash'; -import { IndexField } from '../../../graphql/types'; -import { IIndexPattern } from '../../../../../../../src/plugins/data/common/index_patterns'; - -export interface BrowserField { - aggregatable: boolean; - category: string; - description: string | null; - example: string | number | null; - fields: Readonly>>; - format: string; - indexes: string[]; - name: string; - searchable: boolean; - type: string; -} - -export interface DocValueFields { - field: string; - format: string; -} - -export type BrowserFields = Readonly>>; - -export const getAllBrowserFields = (browserFields: BrowserFields): Array> => - Object.values(browserFields).reduce>>( - (acc, namespace) => [ - ...acc, - ...Object.values(namespace.fields != null ? namespace.fields : {}), - ], - [] - ); - -export const getIndexFields = memoizeOne( - (title: string, fields: IndexField[]): IIndexPattern => - fields && fields.length > 0 - ? { - fields: fields.map((field) => - pick(['name', 'searchable', 'type', 'aggregatable', 'esTypes', 'subType'], field) - ), - title, - } - : { fields: [], title }, - (newArgs, lastArgs) => newArgs[0] === lastArgs[0] && newArgs[1].length === lastArgs[1].length -); - -export const getBrowserFields = memoizeOne( - (_title: string, fields: IndexField[]): BrowserFields => - fields && fields.length > 0 - ? fields.reduce( - (accumulator: BrowserFields, field: IndexField) => - set([field.category, 'fields', field.name], field, accumulator), - {} - ) - : {}, - // Update the value only if _title has changed - (newArgs, lastArgs) => newArgs[0] === lastArgs[0] -); - -export const getDocValueFields = memoizeOne( - (_title: string, fields: IndexField[]): DocValueFields[] => - fields && fields.length > 0 - ? fields.reduce((accumulator: DocValueFields[], field: IndexField) => { - if (field.type === 'date' && accumulator.length < 100) { - const format: string = - field.format != null && !isEmpty(field.format) ? field.format : 'date_time'; - return [ - ...accumulator, - { - field: field.name, - format, - }, - ]; - } - return accumulator; - }, []) - : [], - // Update the value only if _title has changed - (newArgs, lastArgs) => newArgs[0] === lastArgs[0] -); - -export const indicesExistOrDataTemporarilyUnavailable = ( - indicesExist: boolean | null | undefined -) => indicesExist || isUndefined(indicesExist); - -export const EMPTY_BROWSER_FIELDS = {}; -export const EMPTY_DOCVALUE_FIELD: DocValueFields[] = []; diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx index 38af84e0968f8..673db7af2b5e6 100644 --- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx @@ -4,28 +4,73 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + +import React from 'react'; import { act, renderHook } from '@testing-library/react-hooks'; +import { Provider } from 'react-redux'; -import { getSourceDefaults, useSourceManager, UseSourceManager } from '.'; +import { useInitSourcerer } from '.'; +import { mockPatterns, mockSource } from './mocks'; +// import { SourcererScopeName } from '../../store/sourcerer/model'; +import { RouteSpyState } from '../../utils/route/types'; +import { SecurityPageName } from '../../../../common/constants'; +import { createStore, State } from '../../store'; import { - mockSourceSelections, - mockSourceGroup, - mockSourceGroups, - mockPatterns, - mockSource, -} from './mocks'; -import { SecurityPageName } from './constants'; -const mockSourceDefaults = mockSource(SecurityPageName.default); + apolloClientObservable, + createSecuritySolutionStorageMock, + kibanaObservable, + mockGlobalState, + SUB_PLUGINS_REDUCER, +} from '../../mock'; +const mockSourceDefaults = mockSource; + +const mockRouteSpy: RouteSpyState = { + pageName: SecurityPageName.overview, + detailName: undefined, + tabName: undefined, + search: '', + pathName: '/', +}; +const mockDispatch = jest.fn(); +jest.mock('react-redux', () => { + const original = jest.requireActual('react-redux'); + + return { + ...original, + useDispatch: () => mockDispatch, + }; +}); +jest.mock('../../utils/route/use_route_spy', () => ({ + useRouteSpy: () => [mockRouteSpy], +})); jest.mock('../../lib/kibana', () => ({ useKibana: jest.fn().mockReturnValue({ services: { + application: { + capabilities: { + siem: { + crud: true, + }, + }, + }, data: { indexPatterns: { getTitles: jest.fn().mockImplementation(() => Promise.resolve(mockPatterns)), }, + search: { + search: jest.fn().mockImplementation(() => ({ + subscribe: jest.fn().mockImplementation(() => ({ + error: jest.fn(), + next: jest.fn(), + })), + })), + }, }, + notifications: {}, }, }), + useUiSetting$: jest.fn().mockImplementation(() => [mockPatterns]), })); jest.mock('../../utils/apollo_context', () => ({ useApolloClient: jest.fn().mockReturnValue({ @@ -34,148 +79,193 @@ jest.mock('../../utils/apollo_context', () => ({ })); describe('Sourcerer Hooks', () => { - const testId = SecurityPageName.default; - const uninitializedId = SecurityPageName.host; + // const testId = SourcererScopeName.default; + // const uninitializedId = SourcererScopeName.detections; beforeEach(() => { jest.clearAllMocks(); jest.restoreAllMocks(); }); + const state: State = mockGlobalState; + const { storage } = createSecuritySolutionStorageMock(); + let store = createStore( + state, + SUB_PLUGINS_REDUCER, + apolloClientObservable, + kibanaObservable, + storage + ); + + beforeEach(() => { + store = createStore( + state, + SUB_PLUGINS_REDUCER, + apolloClientObservable, + kibanaObservable, + storage + ); + }); describe('Initialization', () => { - it('initializes loading default index patterns', async () => { + it('initializes loading default and timeline index patterns', async () => { await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useSourceManager() - ); - await waitForNextUpdate(); - expect(result.current).toEqual({ - activeSourceGroupId: 'default', - availableIndexPatterns: [], - availableSourceGroupIds: [], - isIndexPatternsLoading: true, - sourceGroups: {}, - getManageSourceGroupById: result.current.getManageSourceGroupById, - initializeSourceGroup: result.current.initializeSourceGroup, - setActiveSourceGroupId: result.current.setActiveSourceGroupId, - updateSourceGroupIndicies: result.current.updateSourceGroupIndicies, + const { waitForNextUpdate } = renderHook(() => useInitSourcerer(), { + wrapper: ({ children }) => {children}, }); - }); - }); - it('initializes loading default source group', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useSourceManager() - ); await waitForNextUpdate(); await waitForNextUpdate(); - expect(result.current).toEqual({ - activeSourceGroupId: 'default', - availableIndexPatterns: mockPatterns, - availableSourceGroupIds: [], - isIndexPatternsLoading: false, - sourceGroups: {}, - getManageSourceGroupById: result.current.getManageSourceGroupById, - initializeSourceGroup: result.current.initializeSourceGroup, - setActiveSourceGroupId: result.current.setActiveSourceGroupId, - updateSourceGroupIndicies: result.current.updateSourceGroupIndicies, + expect(mockDispatch).toBeCalledTimes(2); + expect(mockDispatch.mock.calls[0][0]).toEqual({ + type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', + payload: { id: 'default', loading: true }, }); - }); - }); - it('initialize completes with formatted source group data', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useSourceManager() - ); - await waitForNextUpdate(); - await waitForNextUpdate(); - await waitForNextUpdate(); - expect(result.current).toEqual({ - activeSourceGroupId: testId, - availableIndexPatterns: mockPatterns, - availableSourceGroupIds: [testId], - isIndexPatternsLoading: false, - sourceGroups: { - default: mockSourceGroup(testId), - }, - getManageSourceGroupById: result.current.getManageSourceGroupById, - initializeSourceGroup: result.current.initializeSourceGroup, - setActiveSourceGroupId: result.current.setActiveSourceGroupId, - updateSourceGroupIndicies: result.current.updateSourceGroupIndicies, + expect(mockDispatch.mock.calls[1][0]).toEqual({ + type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', + payload: { id: 'timeline', loading: true }, }); + // expect(mockDispatch.mock.calls[1][0]).toEqual({ + // type: 'x-pack/security_solution/local/sourcerer/SET_INDEX_PATTERNS_LIST', + // payload: { allIndexPatterns: mockPatterns, kibanaIndexPatterns: [] }, + // }); }); }); + // TO DO sourcerer @S + // it('initializes loading default source group', async () => { + // await act(async () => { + // const { result, waitForNextUpdate } = renderHook( + // () => useInitSourcerer(), + // { + // wrapper: ({ children }) => {children}, + // } + // ); + // await waitForNextUpdate(); + // await waitForNextUpdate(); + // expect(result.current).toEqual({ + // activeSourcererScopeId: 'default', + // kibanaIndexPatterns: mockPatterns, + // isIndexPatternsLoading: false, + // getSourcererScopeById: result.current.getSourcererScopeById, + // setActiveSourcererScopeId: result.current.setActiveSourcererScopeId, + // updateSourcererScopeIndices: result.current.updateSourcererScopeIndices, + // }); + // }); + // }); + // it('initialize completes with formatted source group data', async () => { + // await act(async () => { + // const { result, waitForNextUpdate } = renderHook( + // () => useInitSourcerer(), + // { + // wrapper: ({ children }) => {children}, + // } + // ); + // await waitForNextUpdate(); + // await waitForNextUpdate(); + // await waitForNextUpdate(); + // expect(result.current).toEqual({ + // activeSourcererScopeId: testId, + // kibanaIndexPatterns: mockPatterns, + // isIndexPatternsLoading: false, + // getSourcererScopeById: result.current.getSourcererScopeById, + // setActiveSourcererScopeId: result.current.setActiveSourcererScopeId, + // updateSourcererScopeIndices: result.current.updateSourcererScopeIndices, + // }); + // }); + // }); }); - describe('Methods', () => { - it('getManageSourceGroupById: initialized source group returns defaults', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useSourceManager() - ); - await waitForNextUpdate(); - await waitForNextUpdate(); - await waitForNextUpdate(); - const initializedSourceGroup = result.current.getManageSourceGroupById(testId); - expect(initializedSourceGroup).toEqual(mockSourceGroup(testId)); - }); - }); - it('getManageSourceGroupById: uninitialized source group returns defaults', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useSourceManager() - ); - await waitForNextUpdate(); - await waitForNextUpdate(); - await waitForNextUpdate(); - const uninitializedSourceGroup = result.current.getManageSourceGroupById(uninitializedId); - expect(uninitializedSourceGroup).toEqual(getSourceDefaults(uninitializedId, mockPatterns)); - }); - }); - it('initializeSourceGroup: initializes source group', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useSourceManager() - ); - await waitForNextUpdate(); - await waitForNextUpdate(); - await waitForNextUpdate(); - result.current.initializeSourceGroup( - uninitializedId, - mockSourceGroups[uninitializedId], - true - ); - await waitForNextUpdate(); - const initializedSourceGroup = result.current.getManageSourceGroupById(uninitializedId); - expect(initializedSourceGroup.indexPatterns).toEqual(mockSourceSelections[uninitializedId]); - }); - }); - it('setActiveSourceGroupId: active source group id gets set only if it gets initialized first', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useSourceManager() - ); - await waitForNextUpdate(); - expect(result.current.activeSourceGroupId).toEqual(testId); - result.current.setActiveSourceGroupId(uninitializedId); - expect(result.current.activeSourceGroupId).toEqual(testId); - result.current.initializeSourceGroup(uninitializedId); - result.current.setActiveSourceGroupId(uninitializedId); - expect(result.current.activeSourceGroupId).toEqual(uninitializedId); - }); - }); - it('updateSourceGroupIndicies: updates source group indicies', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useSourceManager() - ); - await waitForNextUpdate(); - await waitForNextUpdate(); - await waitForNextUpdate(); - let sourceGroup = result.current.getManageSourceGroupById(testId); - expect(sourceGroup.indexPatterns).toEqual(mockSourceSelections[testId]); - result.current.updateSourceGroupIndicies(testId, ['endgame-*', 'filebeat-*']); - await waitForNextUpdate(); - sourceGroup = result.current.getManageSourceGroupById(testId); - expect(sourceGroup.indexPatterns).toEqual(['endgame-*', 'filebeat-*']); - }); - }); - }); + // describe('Methods', () => { + // it('getSourcererScopeById: initialized source group returns defaults', async () => { + // await act(async () => { + // const { result, waitForNextUpdate } = renderHook( + // () => useInitSourcerer(), + // { + // wrapper: ({ children }) => {children}, + // } + // ); + // await waitForNextUpdate(); + // await waitForNextUpdate(); + // await waitForNextUpdate(); + // const initializedSourcererScope = result.current.getSourcererScopeById(testId); + // expect(initializedSourcererScope).toEqual(mockSourcererScope(testId)); + // }); + // }); + // it('getSourcererScopeById: uninitialized source group returns defaults', async () => { + // await act(async () => { + // const { result, waitForNextUpdate } = renderHook( + // () => useInitSourcerer(), + // { + // wrapper: ({ children }) => {children}, + // } + // ); + // await waitForNextUpdate(); + // await waitForNextUpdate(); + // await waitForNextUpdate(); + // const uninitializedSourcererScope = result.current.getSourcererScopeById(uninitializedId); + // expect(uninitializedSourcererScope).toEqual( + // getSourceDefaults(uninitializedId, mockPatterns) + // ); + // }); + // }); + // // it('initializeSourcererScope: initializes source group', async () => { + // // await act(async () => { + // // const { result, waitForNextUpdate } = renderHook( + // // () => useSourcerer(), + // // { + // // wrapper: ({ children }) => {children}, + // // } + // // ); + // // await waitForNextUpdate(); + // // await waitForNextUpdate(); + // // await waitForNextUpdate(); + // // result.current.initializeSourcererScope( + // // uninitializedId, + // // mockSourcererScopes[uninitializedId], + // // true + // // ); + // // await waitForNextUpdate(); + // // const initializedSourcererScope = result.current.getSourcererScopeById(uninitializedId); + // // expect(initializedSourcererScope.selectedPatterns).toEqual( + // // mockSourcererScopes[uninitializedId] + // // ); + // // }); + // // }); + // it('setActiveSourcererScopeId: active source group id gets set only if it gets initialized first', async () => { + // await act(async () => { + // const { result, waitForNextUpdate } = renderHook( + // () => useInitSourcerer(), + // { + // wrapper: ({ children }) => {children}, + // } + // ); + // await waitForNextUpdate(); + // expect(result.current.activeSourcererScopeId).toEqual(testId); + // result.current.setActiveSourcererScopeId(uninitializedId); + // expect(result.current.activeSourcererScopeId).toEqual(testId); + // // result.current.initializeSourcererScope(uninitializedId); + // result.current.setActiveSourcererScopeId(uninitializedId); + // expect(result.current.activeSourcererScopeId).toEqual(uninitializedId); + // }); + // }); + // it('updateSourcererScopeIndices: updates source group indices', async () => { + // await act(async () => { + // const { result, waitForNextUpdate } = renderHook( + // () => useInitSourcerer(), + // { + // wrapper: ({ children }) => {children}, + // } + // ); + // await waitForNextUpdate(); + // await waitForNextUpdate(); + // await waitForNextUpdate(); + // let sourceGroup = result.current.getSourcererScopeById(testId); + // expect(sourceGroup.selectedPatterns).toEqual(mockSourcererScopes[testId]); + // expect(sourceGroup.scopePatterns).toEqual(mockSourcererScopes[testId]); + // result.current.updateSourcererScopeIndices({ + // id: testId, + // selectedPatterns: ['endgame-*', 'filebeat-*'], + // }); + // await waitForNextUpdate(); + // sourceGroup = result.current.getSourcererScopeById(testId); + // expect(sourceGroup.scopePatterns).toEqual(mockSourcererScopes[testId]); + // expect(sourceGroup.selectedPatterns).toEqual(['endgame-*', 'filebeat-*']); + // }); + // }); + // }); }); diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx index 91907b45aa449..afacd68d71592 100644 --- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx @@ -4,412 +4,72 @@ * you may not use this file except in compliance with the Elastic License. */ -import { get, noop, isEmpty } from 'lodash/fp'; -import React, { createContext, useCallback, useContext, useEffect, useReducer } from 'react'; -import { IIndexPattern } from 'src/plugins/data/public'; - -import { NO_ALERT_INDEX } from '../../../../common/constants'; -import { useKibana } from '../../lib/kibana'; - -import { SourceQuery } from '../../../graphql/types'; - -import { sourceQuery } from '../source/index.gql_query'; -import { useApolloClient } from '../../utils/apollo_context'; -import { - sourceGroups, - SecurityPageName, - SourceGroupsType, - SOURCERER_FEATURE_FLAG_ON, -} from './constants'; -import { - BrowserFields, - DocValueFields, - EMPTY_BROWSER_FIELDS, - EMPTY_DOCVALUE_FIELD, - getBrowserFields, - getDocValueFields, - getIndexFields, - indicesExistOrDataTemporarilyUnavailable, -} from './format'; - -// TYPES -interface ManageSource { - browserFields: BrowserFields; - defaultPatterns: string[]; - docValueFields: DocValueFields[]; - errorMessage: string | null; - id: SourceGroupsType; - indexPattern: IIndexPattern; - indexPatterns: string[]; - indicesExist: boolean | undefined | null; - loading: boolean; -} - -interface ManageSourceInit extends Partial { - id: SourceGroupsType; -} - -type ManageSourceGroupById = { - [id in SourceGroupsType]?: ManageSource; -}; - -type ActionManageSource = - | { - type: 'SET_SOURCE'; - id: SourceGroupsType; - defaultIndex: string[]; - payload: ManageSourceInit; - } - | { - type: 'SET_IS_SOURCE_LOADING'; - id: SourceGroupsType; - payload: boolean; - } - | { - type: 'SET_ACTIVE_SOURCE_GROUP_ID'; - payload: SourceGroupsType; - } - | { - type: 'SET_AVAILABLE_INDEX_PATTERNS'; - payload: string[]; - } - | { - type: 'SET_IS_INDEX_PATTERNS_LOADING'; - payload: boolean; - }; - -interface ManageSourcerer { - activeSourceGroupId: SourceGroupsType; - availableIndexPatterns: string[]; - availableSourceGroupIds: SourceGroupsType[]; - isIndexPatternsLoading: boolean; - sourceGroups: ManageSourceGroupById; -} - -export interface UseSourceManager extends ManageSourcerer { - getManageSourceGroupById: (id: SourceGroupsType) => ManageSource; - initializeSourceGroup: ( - id: SourceGroupsType, - indexToAdd?: string[] | null, - onlyCheckIndexToAdd?: boolean - ) => void; - setActiveSourceGroupId: (id: SourceGroupsType) => void; - updateSourceGroupIndicies: (id: SourceGroupsType, updatedIndicies: string[]) => void; -} - -// DEFAULTS/INIT -export const getSourceDefaults = (id: SourceGroupsType, defaultIndex: string[]) => ({ - browserFields: EMPTY_BROWSER_FIELDS, - defaultPatterns: defaultIndex, - docValueFields: EMPTY_DOCVALUE_FIELD, - errorMessage: null, - id, - indexPattern: getIndexFields(defaultIndex.join(), []), - indexPatterns: defaultIndex, - indicesExist: indicesExistOrDataTemporarilyUnavailable(undefined), - loading: true, -}); - -const initManageSource: ManageSourcerer = { - activeSourceGroupId: SecurityPageName.default, - availableIndexPatterns: [], - availableSourceGroupIds: [], - isIndexPatternsLoading: true, - sourceGroups: {}, -}; -const init: UseSourceManager = { - ...initManageSource, - getManageSourceGroupById: (id: SourceGroupsType) => getSourceDefaults(id, []), - initializeSourceGroup: () => noop, - setActiveSourceGroupId: () => noop, - updateSourceGroupIndicies: () => noop, -}; - -const reducerManageSource = (state: ManageSourcerer, action: ActionManageSource) => { - switch (action.type) { - case 'SET_SOURCE': - return { - ...state, - sourceGroups: { - ...state.sourceGroups, - [action.id]: { - ...getSourceDefaults(action.id, action.defaultIndex), - ...state.sourceGroups[action.id], - ...action.payload, - }, - }, - availableSourceGroupIds: state.availableSourceGroupIds.includes(action.id) - ? state.availableSourceGroupIds - : [...state.availableSourceGroupIds, action.id], - }; - case 'SET_IS_SOURCE_LOADING': - return { - ...state, - sourceGroups: { - ...state.sourceGroups, - [action.id]: { - ...state.sourceGroups[action.id], - id: action.id, - loading: action.payload, - }, - }, - }; - case 'SET_ACTIVE_SOURCE_GROUP_ID': - return { - ...state, - activeSourceGroupId: action.payload, - }; - case 'SET_AVAILABLE_INDEX_PATTERNS': - return { - ...state, - availableIndexPatterns: action.payload, - }; - case 'SET_IS_INDEX_PATTERNS_LOADING': - return { - ...state, - isIndexPatternsLoading: action.payload, - }; - default: - return state; - } -}; - -// HOOKS -export const useSourceManager = (): UseSourceManager => { - const { - services: { - data: { indexPatterns }, - }, - } = useKibana(); - const apolloClient = useApolloClient(); - const [state, dispatch] = useReducer(reducerManageSource, initManageSource); - - // Kibana Index Patterns - const setIsIndexPatternsLoading = useCallback((loading: boolean) => { - dispatch({ - type: 'SET_IS_INDEX_PATTERNS_LOADING', - payload: loading, - }); - }, []); - const getDefaultIndex = useCallback( - (indexToAdd?: string[] | null, onlyCheckIndexToAdd?: boolean) => { - const filterIndexAdd = (indexToAdd ?? []).filter((item) => item !== NO_ALERT_INDEX); - if (!isEmpty(filterIndexAdd)) { - return onlyCheckIndexToAdd - ? filterIndexAdd.sort() - : [ - ...state.availableIndexPatterns, - ...filterIndexAdd.filter((index) => !state.availableIndexPatterns.includes(index)), - ].sort(); - } - return state.availableIndexPatterns.sort(); - }, - [state.availableIndexPatterns] - ); - const setAvailableIndexPatterns = useCallback((availableIndexPatterns: string[]) => { - dispatch({ - type: 'SET_AVAILABLE_INDEX_PATTERNS', - payload: availableIndexPatterns, - }); - }, []); - const fetchKibanaIndexPatterns = useCallback(() => { - setIsIndexPatternsLoading(true); - const abortCtrl = new AbortController(); - - async function fetchTitles() { - try { - const result = await indexPatterns.getTitles(); - setAvailableIndexPatterns(result); - setIsIndexPatternsLoading(false); - } catch (error) { - setIsIndexPatternsLoading(false); - } - } - - fetchTitles(); - - return () => { - return abortCtrl.abort(); - }; - }, [indexPatterns, setAvailableIndexPatterns, setIsIndexPatternsLoading]); - - // Security Solution Source Groups - const setActiveSourceGroupId = useCallback( - (sourceGroupId: SourceGroupsType) => { - if (state.availableSourceGroupIds.includes(sourceGroupId)) { - dispatch({ - type: 'SET_ACTIVE_SOURCE_GROUP_ID', - payload: sourceGroupId, - }); - } - }, - [state.availableSourceGroupIds] - ); - const setIsSourceLoading = useCallback( - ({ id, loading }: { id: SourceGroupsType; loading: boolean }) => { - dispatch({ - type: 'SET_IS_SOURCE_LOADING', - id, - payload: loading, - }); - }, +import deepEqual from 'fast-deep-equal'; +import isEqual from 'lodash/isEqual'; +import { useEffect, useMemo } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; + +import { sourcererActions, sourcererSelectors } from '../../store/sourcerer'; +import { ManageScope, SourcererScopeName } from '../../store/sourcerer/model'; +import { useIndexFields } from '../source'; +import { State } from '../../store'; +import { useUserInfo } from '../../../detections/components/user_info'; + +export const useInitSourcerer = ( + scopeId: SourcererScopeName.default | SourcererScopeName.detections = SourcererScopeName.default +) => { + const dispatch = useDispatch(); + + const { loading: loadingSignalIndex, isSignalIndexExists, signalIndexName } = useUserInfo(); + const getConfigIndexPatternsSelector = useMemo( + () => sourcererSelectors.configIndexPatternsSelector(), [] ); - const enrichSource = useCallback( - (id: SourceGroupsType, indexToAdd?: string[] | null, onlyCheckIndexToAdd?: boolean) => { - let isSubscribed = true; - const abortCtrl = new AbortController(); - const defaultIndex = getDefaultIndex(indexToAdd, onlyCheckIndexToAdd); - const selectedPatterns = defaultIndex.filter((pattern) => - state.availableIndexPatterns.includes(pattern) - ); - if (state.sourceGroups[id] == null) { - dispatch({ - type: 'SET_SOURCE', - id, - defaultIndex: selectedPatterns, - payload: { defaultPatterns: defaultIndex, id }, - }); - } - - async function fetchSource() { - if (!apolloClient) return; - setIsSourceLoading({ id, loading: true }); - try { - const result = await apolloClient.query({ - query: sourceQuery, - fetchPolicy: 'network-only', - variables: { - sourceId: 'default', // always - defaultIndex: selectedPatterns, - }, - context: { - fetchOptions: { - signal: abortCtrl.signal, - }, - }, - }); - if (isSubscribed) { - dispatch({ - type: 'SET_SOURCE', - id, - defaultIndex: selectedPatterns, - payload: { - browserFields: getBrowserFields( - selectedPatterns.join(), - get('data.source.status.indexFields', result) - ), - docValueFields: getDocValueFields( - selectedPatterns.join(), - get('data.source.status.indexFields', result) - ), - errorMessage: null, - id, - indexPattern: getIndexFields( - selectedPatterns.join(), - get('data.source.status.indexFields', result) - ), - indexPatterns: selectedPatterns, - indicesExist: indicesExistOrDataTemporarilyUnavailable( - get('data.source.status.indicesExist', result) - ), - loading: false, - }, - }); - } - } catch (error) { - if (isSubscribed) { - dispatch({ - type: 'SET_SOURCE', - id, - defaultIndex: selectedPatterns, - payload: { - errorMessage: error.message, - id, - loading: false, - }, - }); - } - } - } - - fetchSource(); - - return () => { - isSubscribed = false; - return abortCtrl.abort(); - }; - }, - [ - apolloClient, - getDefaultIndex, - setIsSourceLoading, - state.availableIndexPatterns, - state.sourceGroups, - ] - ); + const ConfigIndexPatterns = useSelector(getConfigIndexPatternsSelector, isEqual); - const initializeSourceGroup = useCallback( - (id: SourceGroupsType, indexToAdd?: string[] | null, onlyCheckIndexToAdd?: boolean) => - enrichSource(id, indexToAdd, onlyCheckIndexToAdd), - [enrichSource] - ); - - const updateSourceGroupIndicies = useCallback( - (id: SourceGroupsType, updatedIndicies: string[]) => enrichSource(id, updatedIndicies, true), - [enrichSource] - ); - const getManageSourceGroupById = useCallback( - (id: SourceGroupsType) => { - const sourceById = state.sourceGroups[id]; - if (sourceById != null) { - return sourceById; - } - return getSourceDefaults(id, getDefaultIndex()); - }, - [getDefaultIndex, state.sourceGroups] - ); + useIndexFields(scopeId); + useIndexFields(SourcererScopeName.timeline); - // load initial default index useEffect(() => { - fetchKibanaIndexPatterns(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + if (!loadingSignalIndex && signalIndexName != null) { + dispatch(sourcererActions.setSignalIndexName({ signalIndexName })); + } + }, [dispatch, loadingSignalIndex, signalIndexName]); + // Related to timeline useEffect(() => { - if (!state.isIndexPatternsLoading) { - Object.entries(sourceGroups).forEach(([key, value]) => - initializeSourceGroup(key as SourceGroupsType, value, true) + if (!loadingSignalIndex && signalIndexName != null) { + dispatch( + sourcererActions.setSelectedIndexPatterns({ + id: SourcererScopeName.timeline, + selectedPatterns: [...ConfigIndexPatterns, signalIndexName], + }) ); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [state.isIndexPatternsLoading]); + }, [ConfigIndexPatterns, dispatch, loadingSignalIndex, signalIndexName]); - return { - ...state, - getManageSourceGroupById, - initializeSourceGroup, - setActiveSourceGroupId, - updateSourceGroupIndicies, - }; + // Related to the detection page + useEffect(() => { + if ( + scopeId === SourcererScopeName.detections && + isSignalIndexExists && + signalIndexName != null + ) { + dispatch( + sourcererActions.setSelectedIndexPatterns({ + id: scopeId, + selectedPatterns: [signalIndexName], + }) + ); + } + }, [dispatch, isSignalIndexExists, scopeId, signalIndexName]); }; -const ManageSourceContext = createContext(init); - -export const useManageSource = () => useContext(ManageSourceContext); - -interface ManageSourceProps { - children: React.ReactNode; -} - -export const MaybeManageSource = ({ children }: ManageSourceProps) => { - const indexPatternManager = useSourceManager(); - return ( - - {children} - +export const useSourcererScope = (scope: SourcererScopeName = SourcererScopeName.default) => { + const sourcererScopeSelector = useMemo(() => sourcererSelectors.getSourcererScopeSelector(), []); + const SourcererScope = useSelector( + (state) => sourcererScopeSelector(state, scope), + deepEqual ); + return SourcererScope; }; -export const ManageSource = SOURCERER_FEATURE_FLAG_ON - ? MaybeManageSource - : ({ children }: ManageSourceProps) => <>{children}; diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/mocks.ts b/x-pack/plugins/security_solution/public/common/containers/sourcerer/mocks.ts index cde14e54694f0..c34a6917f300e 100644 --- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/mocks.ts +++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/mocks.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SecurityPageName } from './constants'; -import { getSourceDefaults } from './index'; +import { initSourcererScope } from '../../store/sourcerer/model'; export const mockPatterns = [ 'auditbeat-*', @@ -14,32 +13,10 @@ export const mockPatterns = [ 'logs-*', 'packetbeat-*', 'winlogbeat-*', + 'journalbeat-*', ]; -export const mockSourceGroups = { - [SecurityPageName.default]: [ - 'apm-*-transaction*', - 'auditbeat-*', - 'blobbeat-*', - 'endgame-*', - 'filebeat-*', - 'logs-*', - 'winlogbeat-*', - ], - [SecurityPageName.host]: [ - 'apm-*-transaction*', - 'endgame-*', - 'logs-*', - 'packetbeat-*', - 'winlogbeat-*', - ], -}; - -export const mockSourceSelections = { - [SecurityPageName.default]: ['auditbeat-*', 'endgame-*', 'filebeat-*', 'logs-*', 'winlogbeat-*'], - [SecurityPageName.host]: ['endgame-*', 'logs-*', 'packetbeat-*', 'winlogbeat-*'], -}; -export const mockSource = (testId: SecurityPageName.default | SecurityPageName.host) => ({ +export const mockSource = { data: { source: { id: 'default', @@ -50,7 +27,7 @@ export const mockSource = (testId: SecurityPageName.default | SecurityPageName.h category: '_id', description: 'Each document has an _id that uniquely identifies it', example: 'Y-6TfmcB0WOhS6qyMv3s', - indexes: mockSourceSelections[testId], + indexes: mockPatterns, name: '_id', searchable: true, type: 'string', @@ -67,48 +44,45 @@ export const mockSource = (testId: SecurityPageName.default | SecurityPageName.h loading: false, networkStatus: 7, stale: false, -}); +}; -export const mockSourceGroup = (testId: SecurityPageName.default | SecurityPageName.host) => { - const indexes = mockSourceSelections[testId]; - return { - ...getSourceDefaults(testId, mockPatterns), - defaultPatterns: mockSourceGroups[testId], - browserFields: { - _id: { - fields: { - _id: { - __typename: 'IndexField', - aggregatable: false, - category: '_id', - description: 'Each document has an _id that uniquely identifies it', - esTypes: null, - example: 'Y-6TfmcB0WOhS6qyMv3s', - format: null, - indexes, - name: '_id', - searchable: true, - subType: null, - type: 'string', - }, - }, - }, - }, - indexPattern: { - fields: [ - { +export const mockSourcererScope = { + ...initSourcererScope, + scopePatterns: mockPatterns, + browserFields: { + _id: { + fields: { + _id: { + __typename: 'IndexField', aggregatable: false, + category: '_id', + description: 'Each document has an _id that uniquely identifies it', esTypes: null, + example: 'Y-6TfmcB0WOhS6qyMv3s', + format: null, + indexes: mockPatterns, name: '_id', searchable: true, subType: null, type: 'string', }, - ], - title: indexes.join(), + }, }, - indexPatterns: indexes, - indicesExist: true, - loading: false, - }; + }, + indexPattern: { + fields: [ + { + aggregatable: false, + esTypes: null, + name: '_id', + searchable: true, + subType: null, + type: 'string', + }, + ], + title: mockPatterns.join(), + }, + selectedPatterns: mockPatterns, + indicesExist: true, + loading: false, }; diff --git a/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts b/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts index 573ef92f7e069..3051459d5de0c 100644 --- a/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts @@ -12,9 +12,25 @@ import { createStartServicesMock, createWithKibanaMock, } from '../kibana_react.mock'; - +const mockStartServicesMock = createStartServicesMock(); export const KibanaServices = { get: jest.fn(), getKibanaVersion: jest.fn(() => '8.0.0') }; -export const useKibana = jest.fn().mockReturnValue({ services: createStartServicesMock() }); +export const useKibana = jest.fn().mockReturnValue({ + services: { + ...mockStartServicesMock, + data: { + ...mockStartServicesMock.data, + search: { + ...mockStartServicesMock.data.search, + search: jest.fn().mockImplementation(() => ({ + subscribe: jest.fn().mockImplementation(() => ({ + error: jest.fn(), + next: jest.fn(), + })), + })), + }, + }, + }, +}); export const useUiSetting = jest.fn(createUseUiSettingMock()); export const useUiSetting$ = jest.fn(createUseUiSetting$Mock()); export const useHttp = jest.fn().mockReturnValue(createStartServicesMock().http); diff --git a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_root_provider.tsx b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_root_provider.tsx index 73c0f00573911..3b7262e8a8d7e 100644 --- a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_root_provider.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_root_provider.tsx @@ -11,7 +11,7 @@ import { Router } from 'react-router-dom'; import { History } from 'history'; import { useObservable } from 'react-use'; import { Store } from 'redux'; -import { EuiThemeProvider } from '../../../../../../legacy/common/eui_styled_components'; +import { EuiThemeProvider } from '../../../../../xpack_legacy/common'; import { KibanaContextProvider } from '../../../../../../../src/plugins/kibana_react/public'; import { RouteCapture } from '../../components/endpoint/route_capture'; import { StartPlugins } from '../../../types'; diff --git a/x-pack/plugins/security_solution/public/common/mock/global_state.ts b/x-pack/plugins/security_solution/public/common/mock/global_state.ts index a74c9a6d2009d..0944b6aa27f67 100644 --- a/x-pack/plugins/security_solution/public/common/mock/global_state.ts +++ b/x-pack/plugins/security_solution/public/common/mock/global_state.ts @@ -22,11 +22,15 @@ import { DEFAULT_TO, DEFAULT_INTERVAL_TYPE, DEFAULT_INTERVAL_VALUE, + DEFAULT_INDEX_PATTERN, } from '../../../common/constants'; import { networkModel } from '../../network/store'; import { TimelineType, TimelineStatus } from '../../../common/types/timeline'; import { mockManagementState } from '../../management/store/reducer'; import { ManagementState } from '../../management/types'; +import { initialSourcererState, SourcererScopeName } from '../store/sourcerer/model'; +import { mockBrowserFields, mockDocValueFields } from '../containers/source/mock'; +import { mockIndexPattern } from './index_pattern'; export const mockGlobalState: State = { app: { @@ -203,6 +207,7 @@ export const mockGlobalState: State = { id: 'test', savedObjectId: null, columns: defaultHeaders, + indexNames: DEFAULT_INDEX_PATTERN, itemsPerPage: 5, dataProviders: [], description: '', @@ -241,6 +246,28 @@ export const mockGlobalState: State = { }, insertTimeline: null, }, + sourcerer: { + ...initialSourcererState, + sourcererScopes: { + ...initialSourcererState.sourcererScopes, + [SourcererScopeName.default]: { + ...initialSourcererState.sourcererScopes[SourcererScopeName.default], + selectedPatterns: DEFAULT_INDEX_PATTERN, + browserFields: mockBrowserFields, + indexPattern: mockIndexPattern, + docValueFields: mockDocValueFields, + loading: false, + }, + [SourcererScopeName.timeline]: { + ...initialSourcererState.sourcererScopes[SourcererScopeName.timeline], + selectedPatterns: DEFAULT_INDEX_PATTERN, + browserFields: mockBrowserFields, + indexPattern: mockIndexPattern, + docValueFields: mockDocValueFields, + loading: false, + }, + }, + }, /** * These state's are wrapped in `Immutable`, but for compatibility with the overall app architecture, * they are cast to mutable versions here. diff --git a/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts b/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts index 826057560f942..e4abc17e9034c 100644 --- a/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts +++ b/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts @@ -4,7 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -export const mockIndexPattern = { +import { IIndexPattern } from '../../../../../../src/plugins/data/common/index_patterns'; + +export const mockIndexPattern: IIndexPattern = { fields: [ { name: '@timestamp', @@ -93,3 +95,5 @@ export const mockIndexPattern = { ], title: 'filebeat-*,auditbeat-*,packetbeat-*', }; + +export const mockIndexNames = ['filebeat-*', 'auditbeat-*', 'packetbeat-*']; diff --git a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts index 26013915315af..6403a50ad4a1d 100644 --- a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts +++ b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts @@ -2124,6 +2124,7 @@ export const mockTimelineModel: TimelineModel = { highlightedDropAndProviderId: '', historyIds: [], id: 'ef579e40-jibber-jabber', + indexNames: [], isFavorite: false, isLive: false, isLoading: false, @@ -2228,6 +2229,7 @@ export const defaultTimelineProps: CreateTimelineProps = { highlightedDropAndProviderId: '', historyIds: [], id: TimelineId.active, + indexNames: [], isFavorite: false, isLive: false, isLoading: false, diff --git a/x-pack/plugins/security_solution/public/common/store/actions.ts b/x-pack/plugins/security_solution/public/common/store/actions.ts index 6b446ab6692d9..f4134b5c47c2c 100644 --- a/x-pack/plugins/security_solution/public/common/store/actions.ts +++ b/x-pack/plugins/security_solution/public/common/store/actions.ts @@ -12,6 +12,7 @@ import { TrustedAppsPageAction } from '../../management/pages/trusted_apps/store export { appActions } from './app'; export { dragAndDropActions } from './drag_and_drop'; export { inputsActions } from './inputs'; +export { sourcererActions } from './sourcerer'; import { RoutingAction } from './routing'; export type AppAction = diff --git a/x-pack/plugins/security_solution/public/common/store/model.ts b/x-pack/plugins/security_solution/public/common/store/model.ts index 0032a95cce321..04603d0607583 100644 --- a/x-pack/plugins/security_solution/public/common/store/model.ts +++ b/x-pack/plugins/security_solution/public/common/store/model.ts @@ -7,4 +7,5 @@ export { appModel } from './app'; export { dragAndDropModel } from './drag_and_drop'; export { inputsModel } from './inputs'; +export { sourcererModel } from './sourcerer'; export * from './types'; diff --git a/x-pack/plugins/security_solution/public/common/store/reducer.ts b/x-pack/plugins/security_solution/public/common/store/reducer.ts index a0977cea71da7..60cb6a4e960bd 100644 --- a/x-pack/plugins/security_solution/public/common/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/common/store/reducer.ts @@ -9,6 +9,7 @@ import { combineReducers, PreloadedState, AnyAction, Reducer } from 'redux'; import { appReducer, initialAppState } from './app'; import { dragAndDropReducer, initialDragAndDropState } from './drag_and_drop'; import { createInitialInputsState, inputsReducer } from './inputs'; +import { sourcererReducer, sourcererModel } from './sourcerer'; import { HostsPluginReducer } from '../../hosts/store'; import { NetworkPluginReducer } from '../../network/store'; @@ -18,6 +19,7 @@ import { SecuritySubPlugins } from '../../app/types'; import { ManagementPluginReducer } from '../../management'; import { State } from './types'; import { AppAction } from './actions'; +import { KibanaIndexPatterns } from './sourcerer/model'; export type SubPluginsInitReducer = HostsPluginReducer & NetworkPluginReducer & @@ -28,13 +30,22 @@ export type SubPluginsInitReducer = HostsPluginReducer & * Factory for the 'initialState' that is used to preload state into the Security App's redux store. */ export const createInitialState = ( - pluginsInitState: SecuritySubPlugins['store']['initialState'] + pluginsInitState: SecuritySubPlugins['store']['initialState'], + { + kibanaIndexPatterns, + configIndexPatterns, + }: { kibanaIndexPatterns: KibanaIndexPatterns; configIndexPatterns: string[] } ): PreloadedState => { const preloadedState: PreloadedState = { app: initialAppState, dragAndDrop: initialDragAndDropState, ...pluginsInitState, inputs: createInitialInputsState(), + sourcerer: { + ...sourcererModel.initialSourcererState, + kibanaIndexPatterns, + configIndexPatterns, + }, }; return preloadedState; }; @@ -49,5 +60,6 @@ export const createReducer: ( app: appReducer, dragAndDrop: dragAndDropReducer, inputs: inputsReducer, + sourcerer: sourcererReducer, ...pluginsReducer, }); diff --git a/x-pack/plugins/security_solution/public/common/store/selectors.ts b/x-pack/plugins/security_solution/public/common/store/selectors.ts index b938bae39b634..3cefd92bf9e60 100644 --- a/x-pack/plugins/security_solution/public/common/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/common/store/selectors.ts @@ -7,3 +7,4 @@ export { appSelectors } from './app'; export { dragAndDropSelectors } from './drag_and_drop'; export { inputsSelectors } from './inputs'; +export { sourcererSelectors } from './sourcerer'; diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/actions.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/actions.ts new file mode 100644 index 0000000000000..0b40586798f09 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/actions.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import actionCreatorFactory from 'typescript-fsa'; +import { TimelineEventsType } from '../../../../common/types/timeline'; + +import { KibanaIndexPatterns, ManageScopeInit, SourcererScopeName } from './model'; + +const actionCreator = actionCreatorFactory('x-pack/security_solution/local/sourcerer'); + +export const setSource = actionCreator<{ + id: SourcererScopeName; + payload: ManageScopeInit; +}>('SET_SOURCE'); + +export const setIndexPatternsList = actionCreator<{ + kibanaIndexPatterns: KibanaIndexPatterns; + configIndexPatterns: string[]; +}>('SET_INDEX_PATTERNS_LIST'); + +export const setSignalIndexName = actionCreator<{ signalIndexName: string }>( + 'SET_SIGNAL_INDEX_NAME' +); + +export const setSourcererScopeLoading = actionCreator<{ id: SourcererScopeName; loading: boolean }>( + 'SET_SOURCERER_SCOPE_LOADING' +); + +export const setSelectedIndexPatterns = actionCreator<{ + id: SourcererScopeName; + selectedPatterns: string[]; + eventType?: TimelineEventsType; +}>('SET_SELECTED_INDEX_PATTERNS'); diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/index.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/index.ts new file mode 100644 index 0000000000000..551c7d8e3efbc --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as sourcererActions from './actions'; +import * as sourcererModel from './model'; +import * as sourcererSelectors from './selectors'; + +export { sourcererActions, sourcererModel, sourcererSelectors }; +export * from './reducer'; diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/model.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/model.ts new file mode 100644 index 0000000000000..93f7ff95dfb00 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/model.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IIndexPattern } from '../../../../../../../src/plugins/data/common/index_patterns'; +import { DocValueFields } from '../../../../common/search_strategy/common'; +import { + BrowserFields, + EMPTY_BROWSER_FIELDS, + EMPTY_DOCVALUE_FIELD, + EMPTY_INDEX_PATTERN, +} from '../../../../common/search_strategy/index_fields'; + +export type ErrorModel = Error[]; + +export enum SourcererScopeName { + default = 'default', + detections = 'detections', + timeline = 'timeline', +} + +export interface ManageScope { + browserFields: BrowserFields; + docValueFields: DocValueFields[]; + errorMessage: string | null; + id: SourcererScopeName; + indexPattern: IIndexPattern; + indicesExist: boolean | undefined | null; + loading: boolean; + selectedPatterns: string[]; +} + +export interface ManageScopeInit extends Partial { + id: SourcererScopeName; +} + +export type SourcererScopeById = { + [id in SourcererScopeName]: ManageScope; +}; + +export type KibanaIndexPatterns = Array<{ id: string; title: string }>; + +// ManageSourcerer +export interface SourcererModel { + kibanaIndexPatterns: KibanaIndexPatterns; + configIndexPatterns: string[]; + signalIndexName: string | null; + sourcererScopes: SourcererScopeById; +} + +export const initSourcererScope = { + browserFields: EMPTY_BROWSER_FIELDS, + docValueFields: EMPTY_DOCVALUE_FIELD, + errorMessage: null, + indexPattern: EMPTY_INDEX_PATTERN, + indicesExist: true, + loading: true, + selectedPatterns: [], +}; + +export const initialSourcererState: SourcererModel = { + kibanaIndexPatterns: [], + configIndexPatterns: [], + signalIndexName: null, + sourcererScopes: { + [SourcererScopeName.default]: { + ...initSourcererScope, + id: SourcererScopeName.default, + }, + [SourcererScopeName.detections]: { + ...initSourcererScope, + id: SourcererScopeName.detections, + }, + [SourcererScopeName.timeline]: { + ...initSourcererScope, + id: SourcererScopeName.timeline, + }, + }, +}; + +export type FSourcererScopePatterns = { + [id in SourcererScopeName]: string[]; +}; +export type SourcererScopePatterns = Partial; diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/reducer.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/reducer.ts new file mode 100644 index 0000000000000..b65d4d6338e50 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/reducer.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import isEmpty from 'lodash/isEmpty'; +import { reducerWithInitialState } from 'typescript-fsa-reducers'; + +import { + setIndexPatternsList, + setSourcererScopeLoading, + setSelectedIndexPatterns, + setSignalIndexName, + setSource, +} from './actions'; +import { initialSourcererState, SourcererModel, SourcererScopeName } from './model'; + +export type SourcererState = SourcererModel; + +export const sourcererReducer = reducerWithInitialState(initialSourcererState) + .case(setIndexPatternsList, (state, { kibanaIndexPatterns, configIndexPatterns }) => ({ + ...state, + kibanaIndexPatterns, + configIndexPatterns, + })) + .case(setSignalIndexName, (state, { signalIndexName }) => ({ + ...state, + signalIndexName, + })) + .case(setSourcererScopeLoading, (state, { id, loading }) => ({ + ...state, + sourcererScopes: { + ...state.sourcererScopes, + [id]: { + ...state.sourcererScopes[id], + loading, + }, + }, + })) + .case(setSelectedIndexPatterns, (state, { id, selectedPatterns, eventType }) => { + const kibanaIndexPatterns = state.kibanaIndexPatterns.map((kip) => kip.title); + const newSelectedPatterns = selectedPatterns.filter( + (sp) => + state.configIndexPatterns.includes(sp) || + kibanaIndexPatterns.includes(sp) || + (!isEmpty(state.signalIndexName) && state.signalIndexName === sp) + ); + let defaultIndexPatterns = state.configIndexPatterns; + if (id === SourcererScopeName.timeline && isEmpty(newSelectedPatterns)) { + if (eventType === 'all' && !isEmpty(state.signalIndexName)) { + defaultIndexPatterns = [...state.configIndexPatterns, state.signalIndexName ?? '']; + } else if (eventType === 'raw') { + defaultIndexPatterns = state.configIndexPatterns; + } else if ( + !isEmpty(state.signalIndexName) && + (eventType === 'signal' || eventType === 'alert') + ) { + defaultIndexPatterns = [state.signalIndexName ?? '']; + } + } else if (id === SourcererScopeName.detections && isEmpty(newSelectedPatterns)) { + defaultIndexPatterns = [state.signalIndexName ?? '']; + } + return { + ...state, + sourcererScopes: { + ...state.sourcererScopes, + [id]: { + ...state.sourcererScopes[id], + selectedPatterns: isEmpty(newSelectedPatterns) + ? defaultIndexPatterns + : newSelectedPatterns, + }, + }, + }; + }) + .case(setSource, (state, { id, payload }) => { + const { ...sourcererScopes } = payload; + return { + ...state, + sourcererScopes: { + ...state.sourcererScopes, + [id]: { + ...state.sourcererScopes[id], + ...sourcererScopes, + ...(state.sourcererScopes[id].selectedPatterns.length === 0 + ? { selectedPatterns: state.configIndexPatterns } + : {}), + }, + }, + }; + }) + .build(); diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.ts new file mode 100644 index 0000000000000..ca9ea26ba5bac --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { createSelector } from 'reselect'; +import { State } from '../types'; +import { SourcererScopeById, KibanaIndexPatterns, SourcererScopeName, ManageScope } from './model'; + +export const sourcererKibanaIndexPatternsSelector = ({ sourcerer }: State): KibanaIndexPatterns => + sourcerer.kibanaIndexPatterns; + +export const sourcererSignalIndexNameSelector = ({ sourcerer }: State): string | null => + sourcerer.signalIndexName; + +export const sourcererConfigIndexPatternsSelector = ({ sourcerer }: State): string[] => + sourcerer.configIndexPatterns; + +export const sourcererScopesSelector = ({ sourcerer }: State): SourcererScopeById => + sourcerer.sourcererScopes; + +export const scopesSelector = () => createSelector(sourcererScopesSelector, (scopes) => scopes); + +export const kibanaIndexPatternsSelector = () => + createSelector( + sourcererKibanaIndexPatternsSelector, + (kibanaIndexPatterns) => kibanaIndexPatterns + ); + +export const signalIndexNameSelector = () => + createSelector(sourcererSignalIndexNameSelector, (signalIndexName) => signalIndexName); + +export const configIndexPatternsSelector = () => + createSelector( + sourcererConfigIndexPatternsSelector, + (configIndexPatterns) => configIndexPatterns + ); + +export const getIndexNamesSelectedSelector = () => { + const getScopesSelector = scopesSelector(); + const getConfigIndexPatternsSelector = configIndexPatternsSelector(); + + const mapStateToProps = (state: State, scopeId: SourcererScopeName): string[] => { + const scope = getScopesSelector(state)[scopeId]; + const configIndexPatterns = getConfigIndexPatternsSelector(state); + + return scope.selectedPatterns.length === 0 ? configIndexPatterns : scope.selectedPatterns; + }; + + return mapStateToProps; +}; + +export const getAllExistingIndexNamesSelector = () => { + const getSignalIndexNameSelector = signalIndexNameSelector(); + const getConfigIndexPatternsSelector = configIndexPatternsSelector(); + + const mapStateToProps = (state: State): string[] => { + const signalIndexName = getSignalIndexNameSelector(state); + const configIndexPatterns = getConfigIndexPatternsSelector(state); + + return signalIndexName != null + ? [...configIndexPatterns, signalIndexName] + : configIndexPatterns; + }; + + return mapStateToProps; +}; + +export const defaultIndexNamesSelector = () => { + const getScopesSelector = scopesSelector(); + const getConfigIndexPatternsSelector = configIndexPatternsSelector(); + + const mapStateToProps = (state: State, scopeId: SourcererScopeName): string[] => { + const scope = getScopesSelector(state)[scopeId]; + const configIndexPatterns = getConfigIndexPatternsSelector(state); + + return scope.selectedPatterns.length === 0 ? configIndexPatterns : scope.selectedPatterns; + }; + + return mapStateToProps; +}; + +export const getSourcererScopeSelector = () => { + const getScopesSelector = scopesSelector(); + + const mapStateToProps = (state: State, scopeId: SourcererScopeName): ManageScope => + getScopesSelector(state)[scopeId]; + + return mapStateToProps; +}; diff --git a/x-pack/plugins/security_solution/public/common/store/types.ts b/x-pack/plugins/security_solution/public/common/store/types.ts index 91d92e4758c4a..6903567c752bc 100644 --- a/x-pack/plugins/security_solution/public/common/store/types.ts +++ b/x-pack/plugins/security_solution/public/common/store/types.ts @@ -12,6 +12,7 @@ import { AppAction } from './actions'; import { Immutable } from '../../../common/endpoint/types'; import { AppState } from './app/reducer'; import { InputsState } from './inputs/reducer'; +import { SourcererState } from './sourcerer/reducer'; import { HostsPluginState } from '../../hosts/store'; import { DragAndDropState } from './drag_and_drop/reducer'; import { TimelinePluginState } from '../../timelines/store/timeline'; @@ -25,6 +26,7 @@ export type StoreState = HostsPluginState & app: AppState; dragAndDrop: DragAndDropState; inputs: InputsState; + sourcerer: SourcererState; }; /** * The redux `State` type for the Security App. diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx index 678aaf06e50e4..e3440f4158513 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx @@ -197,6 +197,7 @@ describe('alert actions', () => { highlightedDropAndProviderId: '', historyIds: [], id: '', + indexNames: [], isFavorite: false, isLive: false, isLoading: false, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index 640726bb2e7c8..7f98d3b2f71de 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -279,6 +279,7 @@ export const sendAlertToTimelineAction = async ({ ...getThresholdAggregationDataProvider(ecsData, nonEcsData), ], id: TimelineId.active, + indexNames: [], dateRange: { start: from, end: to, @@ -329,6 +330,7 @@ export const sendAlertToTimelineAction = async ({ }, ], id: TimelineId.active, + indexNames: [], dateRange: { start: from, end: to, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx index be24957602037..6724d3a83d617 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx @@ -22,7 +22,6 @@ describe('AlertsTableComponent', () => { hasIndexWrite from={'2020-07-07T08:20:18.966Z'} loading - signalsIndex="index" to={'2020-07-08T08:20:18.966Z'} globalQuery={{ query: 'query', diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index 0416b3d2a459f..d66d37a020040 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -13,7 +13,6 @@ import { Status } from '../../../../common/detection_engine/schemas/common/schem import { Filter, esQuery } from '../../../../../../../src/plugins/data/public'; import { TimelineIdLiteral } from '../../../../common/types/timeline'; import { useAppToasts } from '../../../common/hooks/use_app_toasts'; -import { useFetchIndexPatterns } from '../../containers/detection_engine/rules/fetch_index_patterns'; import { StatefulEventsViewer } from '../../../common/components/events_viewer'; import { HeaderSection } from '../../../common/components/header_section'; import { combineQueries } from '../../../timelines/components/timeline/helpers'; @@ -45,6 +44,8 @@ import { displaySuccessToast, displayErrorToast, } from '../../../common/components/toasters'; +import { SourcererScopeName } from '../../../common/store/sourcerer/model'; +import { useSourcererScope } from '../../../common/containers/sourcerer'; interface OwnProps { timelineId: TimelineIdLiteral; @@ -55,7 +56,6 @@ interface OwnProps { loading: boolean; showBuildingBlockAlerts: boolean; onShowBuildingBlockAlertsChanged: (showBuildingBlockAlerts: boolean) => void; - signalsIndex: string; to: string; } @@ -80,19 +80,20 @@ export const AlertsTableComponent: React.FC = ({ setEventsLoading, showBuildingBlockAlerts, onShowBuildingBlockAlertsChanged, - signalsIndex, to, }) => { const [showClearSelectionAction, setShowClearSelectionAction] = useState(false); const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); - const [{ browserFields, indexPatterns, isLoading: indexPatternsLoading }] = useFetchIndexPatterns( - signalsIndex !== '' ? [signalsIndex] : [], - 'alerts_table' - ); + const { + browserFields, + indexPattern: indexPatterns, + loading: indexPatternsLoading, + selectedPatterns, + } = useSourcererScope(SourcererScopeName.detections); const kibana = useKibana(); const [, dispatchToaster] = useStateToaster(); const { addWarning } = useAppToasts(); - const { initializeTimeline, setSelectAll, setIndexToAdd } = useManageTimeline(); + const { initializeTimeline, setSelectAll } = useManageTimeline(); const getGlobalQuery = useCallback( (customFilters: Filter[]) => { @@ -284,7 +285,6 @@ export const AlertsTableComponent: React.FC = ({ ] ); - const defaultIndices = useMemo(() => [signalsIndex], [signalsIndex]); const defaultFiltersMemo = useMemo(() => { if (isEmpty(defaultFilters)) { return buildAlertStatusFilter(filterGroup); @@ -301,7 +301,6 @@ export const AlertsTableComponent: React.FC = ({ filterManager, footerText: i18n.TOTAL_COUNT_OF_ALERTS, id: timelineId, - indexToAdd: defaultIndices, loadingText: i18n.LOADING_ALERTS, selectAll: false, queryFields: requiredFieldsForActions, @@ -310,16 +309,12 @@ export const AlertsTableComponent: React.FC = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - useEffect(() => { - setIndexToAdd({ id: timelineId, indexToAdd: defaultIndices }); - }, [timelineId, defaultIndices, setIndexToAdd]); - const headerFilterGroup = useMemo( () => , [onFilterGroupChangedCallback] ); - if (loading || indexPatternsLoading || isEmpty(signalsIndex)) { + if (loading || indexPatternsLoading || isEmpty(selectedPatterns)) { return ( @@ -330,12 +325,12 @@ export const AlertsTableComponent: React.FC = ({ return ( diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx index 4559e44b8c3c5..82fed152ea66d 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx @@ -109,7 +109,7 @@ const AlertContextMenuComponent: React.FC = ({ const closeAddExceptionModal = useCallback(() => { setShouldShowAddExceptionModal(false); setAddExceptionModalState(addExceptionModalInitialState); - }, [setShouldShowAddExceptionModal, setAddExceptionModalState]); + }, []); const onAddExceptionCancel = useCallback(() => { closeAddExceptionModal(); @@ -305,33 +305,6 @@ const AlertContextMenuComponent: React.FC = ({ [setShouldShowAddExceptionModal, setAddExceptionModalState] ); - const AddExceptionModal = useCallback( - () => - shouldShowAddExceptionModal === true && addExceptionModalState.alertData !== null ? ( - - ) : null, - [ - shouldShowAddExceptionModal, - addExceptionModalState.alertData, - addExceptionModalState.ruleName, - addExceptionModalState.ruleId, - addExceptionModalState.ruleIndices, - addExceptionModalState.exceptionListType, - onAddExceptionCancel, - onAddExceptionConfirm, - alertStatus, - ] - ); - const button = ( = ({
- + {shouldShowAddExceptionModal === true && addExceptionModalState.alertData !== null && ( + + )} ); }; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx index 4ab5fa5e6012f..f4649b016f67c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx @@ -15,7 +15,6 @@ import { useApolloClient } from '../../../../common/utils/apollo_context'; import { sendAlertToTimelineAction } from '../actions'; import { dispatchUpdateTimeline } from '../../../../timelines/components/open_timeline/helpers'; import { ActionIconItem } from '../../../../timelines/components/timeline/body/actions/action_icon_item'; - import { CreateTimelineProps } from '../types'; import { ACTION_INVESTIGATE_IN_TIMELINE, @@ -49,6 +48,8 @@ const InvestigateInTimelineActionComponent: React.FC = (props) => ( - + ); DetectionEngineHeaderPageComponent.defaultProps = { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx index f2eb5cf5b94f3..2ce9d1ea68b3c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx @@ -13,6 +13,7 @@ import { esFilters, FilterManager, UI_SETTINGS, + IndexPattern, } from '../../../../../../../../src/plugins/data/public'; import { SeverityBadge } from '../severity_badge'; @@ -140,11 +141,11 @@ describe('helpers', () => { filterManager: mockFilterManager, query: mockQueryBarWithFilters.query, savedId: mockQueryBarWithFilters.saved_id, - indexPatterns: { + indexPatterns: ({ fields: [{ name: 'event.category', type: 'test type' }], title: 'test title', getFormatterForField: () => ({ convert: (val: unknown) => val }), - }, + } as unknown) as IndexPattern, }); const wrapper = shallow(result[0].description as React.ReactElement); const filterLabelComponent = wrapper.find(esFilters.FilterLabel).at(0); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx index cb25785eaa5b2..4312be0b46990 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx @@ -8,8 +8,9 @@ import { mount, shallow } from 'enzyme'; import { ThemeProvider } from 'styled-components'; import euiDarkVars from '@elastic/eui/dist/eui_theme_light.json'; +import { stubIndexPattern } from 'src/plugins/data/common/index_patterns/index_pattern.stub'; import { StepAboutRule } from '.'; - +import { useFetchIndex } from '../../../../common/containers/source'; import { mockAboutStepRule } from '../../../pages/detection_engine/rules/all/__mocks__/mock'; import { StepRuleDescription } from '../description_step'; import { stepAboutDefaultValue } from './default_value'; @@ -20,6 +21,7 @@ import { } from '../../../pages/detection_engine/rules/types'; import { fillEmptySeverityMappings } from '../../../pages/detection_engine/rules/helpers'; +jest.mock('../../../../common/containers/source'); const theme = () => ({ eui: euiDarkVars, darkMode: true }); /* eslint-disable no-console */ @@ -44,6 +46,12 @@ describe('StepAboutRuleComponent', () => { beforeEach(() => { formHook = null; + (useFetchIndex as jest.Mock).mockImplementation(() => [ + false, + { + indexPatterns: stubIndexPattern, + }, + ]); }); test('it renders StepRuleDescription if isReadOnlyView is true and "name" property exists', () => { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx index 66f95f5ce15d2..90b70e53a459e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx @@ -39,8 +39,8 @@ import { NextStep } from '../next_step'; import { MarkdownEditorForm } from '../../../../common/components/markdown_editor/eui_form'; import { SeverityField } from '../severity_mapping'; import { RiskScoreField } from '../risk_score_mapping'; -import { useFetchIndexPatterns } from '../../../containers/detection_engine/rules'; import { AutocompleteField } from '../autocomplete_field'; +import { useFetchIndex } from '../../../../common/containers/source'; const CommonUseField = getUseField({ component: Field }); @@ -74,10 +74,8 @@ const StepAboutRuleComponent: FC = ({ }) => { const initialState = defaultValues ?? stepAboutDefaultValue; const [severityValue, setSeverityValue] = useState(initialState.severity.value); - const [{ isLoading: indexPatternLoading, indexPatterns }] = useFetchIndexPatterns( - defineRuleData?.index ?? [], - RuleStep.aboutRule - ); + const [indexPatternLoading, { indexPatterns }] = useFetchIndex(defineRuleData?.index ?? []); + const canUseExceptions = defineRuleData?.ruleType && !isMlRule(defineRuleData.ruleType) && diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx index 7846f0c406668..99999ddbf1976 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx @@ -14,7 +14,6 @@ import { DEFAULT_TIMELINE_TITLE } from '../../../../timelines/components/timelin import { isMlRule } from '../../../../../common/machine_learning/helpers'; import { hasMlAdminPermissions } from '../../../../../common/machine_learning/has_ml_admin_permissions'; import { hasMlLicense } from '../../../../../common/machine_learning/has_ml_license'; -import { useFetchIndexPatterns } from '../../../containers/detection_engine/rules'; import { useMlCapabilities } from '../../../../common/components/ml/hooks/use_ml_capabilities'; import { useUiSetting$ } from '../../../../common/lib/kibana'; import { @@ -48,6 +47,7 @@ import { schema } from './schema'; import * as i18n from './translations'; import { isEqlRule, isThresholdRule } from '../../../../../common/detection_engine/utils'; import { EqlQueryBar } from '../eql_query_bar'; +import { useFetchIndex } from '../../../../common/containers/source'; const CommonUseField = getUseField({ component: Field }); @@ -125,10 +125,7 @@ const StepDefineRuleComponent: FC = ({ }) as unknown) as [Partial]; const index = formIndex || initialState.index; const ruleType = formRuleType || initialState.ruleType; - const [{ browserFields, indexPatterns, isLoading: indexPatternsLoading }] = useFetchIndexPatterns( - index, - RuleStep.defineRule - ); + const [indexPatternsLoading, { browserFields, indexPatterns }] = useFetchIndex(index); // reset form when rule type changes useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/detections/components/user_info/index.tsx b/x-pack/plugins/security_solution/public/detections/components/user_info/index.tsx index e1a29c3575d95..00e108ffb89b6 100644 --- a/x-pack/plugins/security_solution/public/detections/components/user_info/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/user_info/index.tsx @@ -169,22 +169,19 @@ export const useUserInfo = (): State => { if (loading !== privilegeLoading || indexNameLoading) { dispatch({ type: 'updateLoading', loading: privilegeLoading || indexNameLoading }); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [loading, privilegeLoading, indexNameLoading]); + }, [dispatch, loading, privilegeLoading, indexNameLoading]); useEffect(() => { if (!loading && hasIndexManage !== hasApiIndexManage && hasApiIndexManage != null) { dispatch({ type: 'updateHasIndexManage', hasIndexManage: hasApiIndexManage }); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [loading, hasIndexManage, hasApiIndexManage]); + }, [dispatch, loading, hasIndexManage, hasApiIndexManage]); useEffect(() => { if (!loading && hasIndexWrite !== hasApiIndexWrite && hasApiIndexWrite != null) { dispatch({ type: 'updateHasIndexWrite', hasIndexWrite: hasApiIndexWrite }); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [loading, hasIndexWrite, hasApiIndexWrite]); + }, [dispatch, loading, hasIndexWrite, hasApiIndexWrite]); useEffect(() => { if ( @@ -194,36 +191,31 @@ export const useUserInfo = (): State => { ) { dispatch({ type: 'updateIsSignalIndexExists', isSignalIndexExists: isApiSignalIndexExists }); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [loading, isSignalIndexExists, isApiSignalIndexExists]); + }, [dispatch, loading, isSignalIndexExists, isApiSignalIndexExists]); useEffect(() => { if (!loading && isAuthenticated !== isApiAuthenticated && isApiAuthenticated != null) { dispatch({ type: 'updateIsAuthenticated', isAuthenticated: isApiAuthenticated }); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [loading, isAuthenticated, isApiAuthenticated]); + }, [dispatch, loading, isAuthenticated, isApiAuthenticated]); useEffect(() => { if (!loading && hasEncryptionKey !== isApiEncryptionKey && isApiEncryptionKey != null) { dispatch({ type: 'updateHasEncryptionKey', hasEncryptionKey: isApiEncryptionKey }); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [loading, hasEncryptionKey, isApiEncryptionKey]); + }, [dispatch, loading, hasEncryptionKey, isApiEncryptionKey]); useEffect(() => { if (!loading && canUserCRUD !== capabilitiesCanUserCRUD && capabilitiesCanUserCRUD != null) { dispatch({ type: 'updateCanUserCRUD', canUserCRUD: capabilitiesCanUserCRUD }); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [loading, canUserCRUD, capabilitiesCanUserCRUD]); + }, [dispatch, loading, canUserCRUD, capabilitiesCanUserCRUD]); useEffect(() => { if (!loading && signalIndexName !== apiSignalIndexName && apiSignalIndexName != null) { dispatch({ type: 'updateSignalIndexName', signalIndexName: apiSignalIndexName }); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [loading, signalIndexName, apiSignalIndexName]); + }, [dispatch, loading, signalIndexName, apiSignalIndexName]); useEffect(() => { if ( diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.test.tsx deleted file mode 100644 index d36c19a6a35c6..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.test.tsx +++ /dev/null @@ -1,475 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { renderHook, act } from '@testing-library/react-hooks'; - -import { DEFAULT_INDEX_PATTERN } from '../../../../../common/constants'; -import { useApolloClient } from '../../../../common/utils/apollo_context'; -import { mocksSource } from '../../../../common/containers/source/mock'; - -import { useFetchIndexPatterns, Return } from './fetch_index_patterns'; - -const mockUseApolloClient = useApolloClient as jest.Mock; -jest.mock('../../../../common/utils/apollo_context'); - -describe('useFetchIndexPatterns', () => { - beforeEach(() => { - mockUseApolloClient.mockClear(); - }); - test('happy path', async () => { - await act(async () => { - mockUseApolloClient.mockImplementation(() => ({ - query: () => Promise.resolve(mocksSource[0].result), - })); - const { result, waitForNextUpdate } = renderHook(() => - useFetchIndexPatterns(DEFAULT_INDEX_PATTERN) - ); - await waitForNextUpdate(); - await waitForNextUpdate(); - - expect(result.current).toEqual([ - { - browserFields: { - base: { - fields: { - '@timestamp': { - category: 'base', - description: - 'Date/time when the event originated. For log events this is the date/time when the event was generated, and not when it was read. Required field for all events.', - example: '2016-05-23T08:05:34.853Z', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: '@timestamp', - searchable: true, - type: 'date', - aggregatable: true, - }, - }, - }, - agent: { - fields: { - 'agent.ephemeral_id': { - category: 'agent', - description: - 'Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but `agent.id` does not.', - example: '8a4f500f', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'agent.ephemeral_id', - searchable: true, - type: 'string', - aggregatable: true, - }, - 'agent.hostname': { - category: 'agent', - description: null, - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'agent.hostname', - searchable: true, - type: 'string', - aggregatable: true, - }, - 'agent.id': { - category: 'agent', - description: - 'Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.', - example: '8a4f500d', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'agent.id', - searchable: true, - type: 'string', - aggregatable: true, - }, - 'agent.name': { - category: 'agent', - description: - 'Name of the agent. This is a name that can be given to an agent. This can be helpful if for example two Filebeat instances are running on the same host but a human readable separation is needed on which Filebeat instance data is coming from. If no name is given, the name is often left empty.', - example: 'foo', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'agent.name', - searchable: true, - type: 'string', - aggregatable: true, - }, - }, - }, - auditd: { - fields: { - 'auditd.data.a0': { - category: 'auditd', - description: null, - example: null, - format: '', - indexes: ['auditbeat'], - name: 'auditd.data.a0', - searchable: true, - type: 'string', - aggregatable: true, - }, - 'auditd.data.a1': { - category: 'auditd', - description: null, - example: null, - format: '', - indexes: ['auditbeat'], - name: 'auditd.data.a1', - searchable: true, - type: 'string', - aggregatable: true, - }, - 'auditd.data.a2': { - category: 'auditd', - description: null, - example: null, - format: '', - indexes: ['auditbeat'], - name: 'auditd.data.a2', - searchable: true, - type: 'string', - aggregatable: true, - }, - }, - }, - client: { - fields: { - 'client.address': { - category: 'client', - description: - 'Some event client addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the `.address` field. Then it should be duplicated to `.ip` or `.domain`, depending on which one it is.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'client.address', - searchable: true, - type: 'string', - aggregatable: true, - }, - 'client.bytes': { - category: 'client', - description: 'Bytes sent from the client to the server.', - example: '184', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'client.bytes', - searchable: true, - type: 'number', - aggregatable: true, - }, - 'client.domain': { - category: 'client', - description: 'Client domain.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'client.domain', - searchable: true, - type: 'string', - aggregatable: true, - }, - 'client.geo.country_iso_code': { - category: 'client', - description: 'Country ISO code.', - example: 'CA', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'client.geo.country_iso_code', - searchable: true, - type: 'string', - aggregatable: true, - }, - }, - }, - cloud: { - fields: { - 'cloud.account.id': { - category: 'cloud', - description: - 'The cloud account or organization id used to identify different entities in a multi-tenant environment. Examples: AWS account id, Google Cloud ORG Id, or other unique identifier.', - example: '666777888999', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'cloud.account.id', - searchable: true, - type: 'string', - aggregatable: true, - }, - 'cloud.availability_zone': { - category: 'cloud', - description: 'Availability zone in which this host is running.', - example: 'us-east-1c', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'cloud.availability_zone', - searchable: true, - type: 'string', - aggregatable: true, - }, - }, - }, - container: { - fields: { - 'container.id': { - category: 'container', - description: 'Unique container id.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'container.id', - searchable: true, - type: 'string', - aggregatable: true, - }, - 'container.image.name': { - category: 'container', - description: 'Name of the image the container was built on.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'container.image.name', - searchable: true, - type: 'string', - aggregatable: true, - }, - 'container.image.tag': { - category: 'container', - description: 'Container image tag.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'container.image.tag', - searchable: true, - type: 'string', - aggregatable: true, - }, - }, - }, - destination: { - fields: { - 'destination.address': { - category: 'destination', - description: - 'Some event destination addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the `.address` field. Then it should be duplicated to `.ip` or `.domain`, depending on which one it is.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'destination.address', - searchable: true, - type: 'string', - aggregatable: true, - }, - 'destination.bytes': { - category: 'destination', - description: 'Bytes sent from the destination to the source.', - example: '184', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'destination.bytes', - searchable: true, - type: 'number', - aggregatable: true, - }, - 'destination.domain': { - category: 'destination', - description: 'Destination domain.', - example: null, - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'destination.domain', - searchable: true, - type: 'string', - aggregatable: true, - }, - 'destination.ip': { - aggregatable: true, - category: 'destination', - description: - 'IP address of the destination. Can be one or multiple IPv4 or IPv6 addresses.', - example: '', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'destination.ip', - searchable: true, - type: 'ip', - }, - 'destination.port': { - aggregatable: true, - category: 'destination', - description: 'Port of the destination.', - example: '', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'destination.port', - searchable: true, - type: 'long', - }, - }, - }, - source: { - fields: { - 'source.ip': { - aggregatable: true, - category: 'source', - description: - 'IP address of the source. Can be one or multiple IPv4 or IPv6 addresses.', - example: '', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'source.ip', - searchable: true, - type: 'ip', - }, - 'source.port': { - aggregatable: true, - category: 'source', - description: 'Port of the source.', - example: '', - format: '', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], - name: 'source.port', - searchable: true, - type: 'long', - }, - }, - }, - event: { - fields: { - 'event.end': { - aggregatable: true, - category: 'event', - description: - 'event.end contains the date when the event ended or when the activity was last observed.', - example: null, - format: '', - indexes: [ - 'apm-*-transaction*', - 'auditbeat-*', - 'endgame-*', - 'filebeat-*', - 'logs-*', - 'packetbeat-*', - 'winlogbeat-*', - ], - name: 'event.end', - searchable: true, - type: 'date', - }, - }, - }, - }, - isLoading: false, - indices: [ - 'apm-*-transaction*', - 'auditbeat-*', - 'endgame-*', - 'filebeat-*', - 'logs-*', - 'packetbeat-*', - 'winlogbeat-*', - ], - indicesExists: true, - docValueFields: [ - { - field: '@timestamp', - format: 'date_time', - }, - { - field: 'event.end', - format: 'date_time', - }, - ], - indexPatterns: { - fields: [ - { name: '@timestamp', searchable: true, type: 'date', aggregatable: true }, - { name: 'agent.ephemeral_id', searchable: true, type: 'string', aggregatable: true }, - { name: 'agent.hostname', searchable: true, type: 'string', aggregatable: true }, - { name: 'agent.id', searchable: true, type: 'string', aggregatable: true }, - { name: 'agent.name', searchable: true, type: 'string', aggregatable: true }, - { name: 'auditd.data.a0', searchable: true, type: 'string', aggregatable: true }, - { name: 'auditd.data.a1', searchable: true, type: 'string', aggregatable: true }, - { name: 'auditd.data.a2', searchable: true, type: 'string', aggregatable: true }, - { name: 'client.address', searchable: true, type: 'string', aggregatable: true }, - { name: 'client.bytes', searchable: true, type: 'number', aggregatable: true }, - { name: 'client.domain', searchable: true, type: 'string', aggregatable: true }, - { - name: 'client.geo.country_iso_code', - searchable: true, - type: 'string', - aggregatable: true, - }, - { name: 'cloud.account.id', searchable: true, type: 'string', aggregatable: true }, - { - name: 'cloud.availability_zone', - searchable: true, - type: 'string', - aggregatable: true, - }, - { name: 'container.id', searchable: true, type: 'string', aggregatable: true }, - { - name: 'container.image.name', - searchable: true, - type: 'string', - aggregatable: true, - }, - { name: 'container.image.tag', searchable: true, type: 'string', aggregatable: true }, - { name: 'destination.address', searchable: true, type: 'string', aggregatable: true }, - { name: 'destination.bytes', searchable: true, type: 'number', aggregatable: true }, - { name: 'destination.domain', searchable: true, type: 'string', aggregatable: true }, - { name: 'destination.ip', searchable: true, type: 'ip', aggregatable: true }, - { name: 'destination.port', searchable: true, type: 'long', aggregatable: true }, - { name: 'source.ip', searchable: true, type: 'ip', aggregatable: true }, - { name: 'source.port', searchable: true, type: 'long', aggregatable: true }, - { name: 'event.end', searchable: true, type: 'date', aggregatable: true }, - ], - title: - 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*', - }, - }, - result.current[1], - ]); - }); - }); - - test('unhappy path', async () => { - await act(async () => { - mockUseApolloClient.mockImplementation(() => ({ - query: () => Promise.reject(new Error('Something went wrong')), - })); - const { result, waitForNextUpdate } = renderHook(() => - useFetchIndexPatterns(DEFAULT_INDEX_PATTERN) - ); - - await waitForNextUpdate(); - await waitForNextUpdate(); - - expect(result.current).toEqual([ - { - browserFields: {}, - docValueFields: [], - indexPatterns: { - fields: [], - title: '', - }, - indices: [ - 'apm-*-transaction*', - 'auditbeat-*', - 'endgame-*', - 'filebeat-*', - 'logs-*', - 'packetbeat-*', - 'winlogbeat-*', - ], - indicesExists: false, - isLoading: false, - }, - result.current[1], - ]); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.tsx deleted file mode 100644 index 82c9292af7451..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.tsx +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { isEmpty, get } from 'lodash/fp'; -import { useEffect, useState, Dispatch, SetStateAction } from 'react'; -import deepEqual from 'fast-deep-equal'; - -import { IIndexPattern } from '../../../../../../../../src/plugins/data/public'; -import { - BrowserFields, - getBrowserFields, - getDocValueFields, - getIndexFields, - sourceQuery, - DocValueFields, -} from '../../../../common/containers/source'; -import { errorToToaster, useStateToaster } from '../../../../common/components/toasters'; -import { SourceQuery } from '../../../../graphql/types'; -import { useApolloClient } from '../../../../common/utils/apollo_context'; - -import * as i18n from './translations'; - -interface FetchIndexPatternReturn { - browserFields: BrowserFields; - docValueFields: DocValueFields[]; - isLoading: boolean; - indices: string[]; - indicesExists: boolean; - indexPatterns: IIndexPattern; -} - -export type Return = [FetchIndexPatternReturn, Dispatch>]; - -const DEFAULT_BROWSER_FIELDS = {}; -const DEFAULT_INDEX_PATTERNS = { fields: [], title: '' }; -const DEFAULT_DOC_VALUE_FIELDS: DocValueFields[] = []; - -// Fun fact: When using this hook multiple times within a component (e.g. add_exception_modal & edit_exception_modal), -// the apolloClient will perform queryDeduplication and prevent the first query from executing. A deep compare is not -// performed on `indices`, so another field must be passed to circumvent this. -// For details, see https://github.com/apollographql/react-apollo/issues/2202 -export const useFetchIndexPatterns = ( - defaultIndices: string[] = [], - queryDeduplication?: string -): Return => { - const apolloClient = useApolloClient(); - const [indices, setIndices] = useState(defaultIndices); - - const [state, setState] = useState({ - browserFields: DEFAULT_BROWSER_FIELDS, - docValueFields: DEFAULT_DOC_VALUE_FIELDS, - indices: defaultIndices, - indicesExists: false, - indexPatterns: DEFAULT_INDEX_PATTERNS, - isLoading: false, - }); - - const [, dispatchToaster] = useStateToaster(); - - useEffect(() => { - if (!deepEqual(defaultIndices, indices)) { - setIndices(defaultIndices); - setState((prevState) => ({ ...prevState, indices: defaultIndices })); - } - }, [defaultIndices, indices]); - - useEffect(() => { - let isSubscribed = true; - const abortCtrl = new AbortController(); - - async function fetchIndexPatterns() { - if (apolloClient && !isEmpty(indices)) { - setState((prevState) => ({ ...prevState, isLoading: true })); - apolloClient - .query({ - query: sourceQuery, - fetchPolicy: 'cache-first', - variables: { - sourceId: 'default', - defaultIndex: indices, - ...(queryDeduplication != null ? { queryDeduplication } : {}), - }, - context: { - fetchOptions: { - signal: abortCtrl.signal, - }, - }, - }) - .then( - (result) => { - if (isSubscribed) { - setState({ - browserFields: getBrowserFields( - indices.join(), - get('data.source.status.indexFields', result) - ), - docValueFields: getDocValueFields( - indices.join(), - get('data.source.status.indexFields', result) - ), - indices, - isLoading: false, - indicesExists: get('data.source.status.indicesExist', result), - indexPatterns: getIndexFields( - indices.join(), - get('data.source.status.indexFields', result) - ), - }); - } - }, - (error) => { - if (isSubscribed) { - setState((prevState) => ({ ...prevState, isLoading: false })); - errorToToaster({ title: i18n.RULE_ADD_FAILURE, error, dispatchToaster }); - } - } - ); - } - } - fetchIndexPatterns(); - return () => { - isSubscribed = false; - abortCtrl.abort(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [indices]); - - return [state, setIndices]; -}; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/index.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/index.ts index a40ab2e487851..930391261ac87 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/index.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/index.ts @@ -5,7 +5,6 @@ */ export * from './api'; -export * from './fetch_index_patterns'; export * from './use_update_rule'; export * from './use_create_rule'; export * from './types'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx index 8c21f6a1e8cb7..a5d21d2847586 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx @@ -20,7 +20,7 @@ import { import { setAbsoluteRangeDatePicker } from '../../../common/store/inputs/actions'; import { DetectionEnginePageComponent } from './detection_engine'; import { useUserData } from '../../components/user_info'; -import { useWithSource } from '../../../common/containers/source'; +import { useSourcererScope } from '../../../common/containers/sourcerer'; import { createStore, State } from '../../../common/store'; import { mockHistory, Router } from '../../../cases/components/__mock__/router'; @@ -34,7 +34,7 @@ jest.mock('../../../common/components/query_bar', () => ({ })); jest.mock('../../containers/detection_engine/lists/use_lists_config'); jest.mock('../../components/user_info'); -jest.mock('../../../common/containers/source'); +jest.mock('../../../common/containers/sourcerer'); jest.mock('../../../common/components/link_to'); jest.mock('../../../common/containers/use_global_time', () => ({ useGlobalTime: jest.fn().mockReturnValue({ @@ -74,7 +74,7 @@ describe('DetectionEnginePageComponent', () => { beforeAll(() => { (useParams as jest.Mock).mockReturnValue({}); (useUserData as jest.Mock).mockReturnValue([{}]); - (useWithSource as jest.Mock).mockReturnValue({ + (useSourcererScope as jest.Mock).mockReturnValue({ indicesExist: true, indexPattern: {}, }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx index 3a3854f145db3..b39cd37521602 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx @@ -13,7 +13,6 @@ import { useHistory } from 'react-router-dom'; import { SecurityPageName } from '../../../app/types'; import { TimelineId } from '../../../../common/types/timeline'; import { useGlobalTime } from '../../../common/containers/use_global_time'; -import { useWithSource } from '../../../common/containers/source'; import { UpdateDateRange } from '../../../common/components/charts/common'; import { FiltersGlobal } from '../../../common/components/filters_global'; import { getRulesUrl } from '../../../common/components/link_to/redirect_to_detection_engine'; @@ -46,6 +45,8 @@ import { timelineSelectors } from '../../../timelines/store/timeline'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; import { TimelineModel } from '../../../timelines/store/timeline/model'; import { buildShowBuildingBlockFilter } from '../../components/alerts_table/default_config'; +import { useSourcererScope } from '../../../common/containers/sourcerer'; +import { SourcererScopeName } from '../../../common/store/sourcerer/model'; export const DetectionEnginePageComponent: React.FC = ({ filters, @@ -117,10 +118,7 @@ export const DetectionEnginePageComponent: React.FC = ({ [setShowBuildingBlockAlerts] ); - const indexToAdd = useMemo(() => (signalIndexName == null ? [] : [signalIndexName]), [ - signalIndexName, - ]); - const { indicesExist, indexPattern } = useWithSource('default', indexToAdd); + const { indicesExist, indexPattern } = useSourcererScope(SourcererScopeName.detections); if (isUserAuthenticated != null && !isUserAuthenticated && !loading) { return ( @@ -202,7 +200,6 @@ export const DetectionEnginePageComponent: React.FC = ({ defaultFilters={alertsTableDefaultFilters} showBuildingBlockAlerts={showBuildingBlockAlerts} onShowBuildingBlockAlertsChanged={onShowBuildingBlockAlertsChangedCallback} - signalsIndex={signalIndexName ?? ''} to={to} /> diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx index f8f9da78b2a06..22c3c43fb2356 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx @@ -20,7 +20,7 @@ import { RuleDetailsPageComponent } from './index'; import { createStore, State } from '../../../../../common/store'; import { setAbsoluteRangeDatePicker } from '../../../../../common/store/inputs/actions'; import { useUserData } from '../../../../components/user_info'; -import { useWithSource } from '../../../../../common/containers/source'; +import { useSourcererScope } from '../../../../../common/containers/sourcerer'; import { useParams } from 'react-router-dom'; import { mockHistory, Router } from '../../../../../cases/components/__mock__/router'; @@ -35,7 +35,7 @@ jest.mock('../../../../../common/components/query_bar', () => ({ jest.mock('../../../../containers/detection_engine/lists/use_lists_config'); jest.mock('../../../../../common/components/link_to'); jest.mock('../../../../components/user_info'); -jest.mock('../../../../../common/containers/source'); +jest.mock('../../../../../common/containers/sourcerer'); jest.mock('../../../../../common/containers/use_global_time', () => ({ useGlobalTime: jest.fn().mockReturnValue({ from: '2020-07-07T08:20:18.966Z', @@ -71,7 +71,7 @@ describe('RuleDetailsPageComponent', () => { beforeAll(() => { (useUserData as jest.Mock).mockReturnValue([{}]); (useParams as jest.Mock).mockReturnValue({}); - (useWithSource as jest.Mock).mockReturnValue({ + (useSourcererScope as jest.Mock).mockReturnValue({ indicesExist: true, indexPattern: {}, }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index 68799f46eee57..ad8ab3ed3a148 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx @@ -36,10 +36,7 @@ import { SiemSearchBar } from '../../../../../common/components/search_bar'; import { WrapperPage } from '../../../../../common/components/wrapper_page'; import { Rule } from '../../../../containers/detection_engine/rules'; import { useListsConfig } from '../../../../containers/detection_engine/lists/use_lists_config'; - -import { useWithSource } from '../../../../../common/containers/source'; import { SpyRoute } from '../../../../../common/utils/route/spy_routes'; - import { StepAboutRuleToggleDetails } from '../../../../components/rules/step_about_rule_details'; import { DetectionEngineHeaderPage } from '../../../../components/detection_engine_header_page'; import { AlertsHistogramPanel } from '../../../../components/alerts_histogram_panel'; @@ -89,6 +86,9 @@ import { showGlobalFilters } from '../../../../../timelines/components/timeline/ import { timelineSelectors } from '../../../../../timelines/store/timeline'; import { timelineDefaults } from '../../../../../timelines/store/timeline/defaults'; import { TimelineModel } from '../../../../../timelines/store/timeline/model'; +import { useSourcererScope } from '../../../../../common/containers/sourcerer'; +import { SourcererScopeName } from '../../../../../common/store/sourcerer/model'; +import { AlertsHistogramOption } from '../../../../components/alerts_histogram_panel/types'; enum RuleDetailTabs { alerts = 'alerts', @@ -265,10 +265,6 @@ export const RuleDetailsPageComponent: FC = ({ [rule, ruleDetailTab] ); - const indexToAdd = useMemo(() => (signalIndexName == null ? [] : [signalIndexName]), [ - signalIndexName, - ]); - const updateDateRangeCallback = useCallback( ({ x }) => { if (!x) { @@ -308,7 +304,7 @@ export const RuleDetailsPageComponent: FC = ({ [setShowBuildingBlockAlerts] ); - const { indicesExist, indexPattern } = useWithSource('default', indexToAdd); + const { indicesExist, indexPattern } = useSourcererScope(SourcererScopeName.detections); const exceptionLists = useMemo((): { lists: ExceptionIdentifiers[]; @@ -350,6 +346,11 @@ export const RuleDetailsPageComponent: FC = ({ return null; } + const defaultRuleStackByOption: AlertsHistogramOption = { + text: 'event.category', + value: 'event.category', + }; + return ( <> {hasIndexWrite != null && !hasIndexWrite && } @@ -485,6 +486,7 @@ export const RuleDetailsPageComponent: FC = ({ signalIndexName={signalIndexName} setQuery={setQuery} stackByOptions={alertsHistogramOptions} + defaultStackByOption={defaultRuleStackByOption} to={to} updateDateRange={updateDateRangeCallback} /> @@ -500,7 +502,6 @@ export const RuleDetailsPageComponent: FC = ({ loading={loading} showBuildingBlockAlerts={showBuildingBlockAlerts} onShowBuildingBlockAlertsChanged={onShowBuildingBlockAlertsChangedCallback} - signalsIndex={signalIndexName ?? ''} to={to} /> )} diff --git a/x-pack/plugins/security_solution/public/graphql/introspection.json b/x-pack/plugins/security_solution/public/graphql/introspection.json index b32083fec1b5e..2f312c461ff8c 100644 --- a/x-pack/plugins/security_solution/public/graphql/introspection.json +++ b/x-pack/plugins/security_solution/public/graphql/introspection.json @@ -2088,262 +2088,6 @@ "isDeprecated": false, "deprecationReason": null }, - { - "name": "OverviewNetwork", - "description": "", - "args": [ - { - "name": "id", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "timerange", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "TimerangeInput", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "filterQuery", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "defaultIndex", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "defaultValue": null - } - ], - "type": { "kind": "OBJECT", "name": "OverviewNetworkData", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "OverviewHost", - "description": "", - "args": [ - { - "name": "id", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "timerange", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "TimerangeInput", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "filterQuery", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "defaultIndex", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "defaultValue": null - } - ], - "type": { "kind": "OBJECT", "name": "OverviewHostData", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "Tls", - "description": "", - "args": [ - { - "name": "filterQuery", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "id", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "ip", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "pagination", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "PaginationInputPaginated", - "ofType": null - } - }, - "defaultValue": null - }, - { - "name": "sort", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "TlsSortField", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "flowTarget", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "FlowTargetSourceDest", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "timerange", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "TimerangeInput", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "defaultIndex", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "TlsData", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "UncommonProcesses", - "description": "Gets UncommonProcesses based on a timerange, or all UncommonProcesses if no criteria is specified", - "args": [ - { - "name": "timerange", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "TimerangeInput", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "pagination", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "PaginationInputPaginated", - "ofType": null - } - }, - "defaultValue": null - }, - { - "name": "filterQuery", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "defaultIndex", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "UncommonProcessesData", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, { "name": "whoAmI", "description": "Just a simple example to get the app name", @@ -2540,7 +2284,7 @@ "ofType": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "IndexField", "ofType": null } + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } } }, @@ -2564,158 +2308,11 @@ "possibleTypes": null }, { - "kind": "OBJECT", - "name": "IndexField", - "description": "A descriptor of a field in an index", - "fields": [ - { - "name": "category", - "description": "Where the field belong", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "example", - "description": "Example of field's value", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "indexes", - "description": "whether the field's belong to an alias index", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "name", - "description": "The name of the field", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "type", - "description": "The type of the field's values as recognized by Kibana", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "searchable", - "description": "Whether the field's values can be efficiently searched for", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "aggregatable", - "description": "Whether the field's values can be aggregated", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "description", - "description": "Description of the field", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "format", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "esTypes", - "description": "the elastic type as mapped in the index", - "args": [], - "type": { "kind": "SCALAR", "name": "ToStringArrayNoNullable", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "subType", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "ToIFieldSubTypeNonNullable", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "ToStringArrayNoNullable", - "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "ToIFieldSubTypeNonNullable", - "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "TimerangeInput", - "description": "", - "fields": null, - "inputFields": [ + "kind": "INPUT_OBJECT", + "name": "TimerangeInput", + "description": "", + "fields": null, + "inputFields": [ { "name": "interval", "description": "The interval string to use for last bucket. The format is '{value}{unit}'. For example '5m' would return the metrics for the last 5 minutes of the timespan.", @@ -8510,856 +8107,7 @@ "deprecationReason": null }, { - "name": "flows", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "destination_ips", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "AutonomousSystemItem", - "description": "", - "fields": [ - { - "name": "name", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "number", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "TopNFlowItemDestination", - "description": "", - "fields": [ - { - "name": "autonomous_system", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "AutonomousSystemItem", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "domain", - "description": "", - "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "ip", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "location", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "GeoItem", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "flows", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "source_ips", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "NetworkDnsSortField", - "description": "", - "fields": null, - "inputFields": [ - { - "name": "field", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "NetworkDnsFields", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "direction", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "Direction", "ofType": null } - }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "ENUM", - "name": "NetworkDnsFields", - "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ - { - "name": "dnsName", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "queryCount", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "uniqueDomains", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "dnsBytesIn", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "dnsBytesOut", - "description": "", - "isDeprecated": false, - "deprecationReason": null - } - ], - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "NetworkDnsData", - "description": "", - "fields": [ - { - "name": "edges", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "NetworkDnsEdges", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "totalCount", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "pageInfo", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "PageInfoPaginated", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "inspect", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "histogram", - "description": "", - "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "MatrixOverOrdinalHistogramData", - "ofType": null - } - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "NetworkDnsEdges", - "description": "", - "fields": [ - { - "name": "node", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "NetworkDnsItem", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "cursor", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "CursorType", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "NetworkDnsItem", - "description": "", - "fields": [ - { - "name": "_id", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "dnsBytesIn", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "dnsBytesOut", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "dnsName", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "queryCount", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "uniqueDomains", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "MatrixOverOrdinalHistogramData", - "description": "", - "fields": [ - { - "name": "x", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "y", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "g", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "NetworkDsOverTimeData", - "description": "", - "fields": [ - { - "name": "inspect", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "matrixHistogramData", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "MatrixOverTimeHistogramData", - "ofType": null - } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "totalCount", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "NetworkHttpSortField", - "description": "", - "fields": null, - "inputFields": [ - { - "name": "direction", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "Direction", "ofType": null } - }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "NetworkHttpData", - "description": "", - "fields": [ - { - "name": "edges", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "NetworkHttpEdges", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "totalCount", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "pageInfo", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "PageInfoPaginated", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "inspect", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "NetworkHttpEdges", - "description": "", - "fields": [ - { - "name": "node", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "NetworkHttpItem", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "cursor", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "CursorType", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "NetworkHttpItem", - "description": "", - "fields": [ - { - "name": "_id", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "domains", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "lastHost", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "lastSourceIp", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "methods", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "path", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "requestCount", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "statuses", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "OverviewNetworkData", - "description": "", - "fields": [ - { - "name": "auditbeatSocket", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "filebeatCisco", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "filebeatNetflow", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "filebeatPanw", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "filebeatSuricata", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "filebeatZeek", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "packetbeatDNS", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "packetbeatFlow", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "packetbeatTLS", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "inspect", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "OverviewHostData", - "description": "", - "fields": [ - { - "name": "auditbeatAuditd", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "auditbeatFIM", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "auditbeatLogin", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "auditbeatPackage", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "auditbeatProcess", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "auditbeatUser", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "endgameDns", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "endgameFile", + "name": "flows", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, @@ -9367,63 +8115,93 @@ "deprecationReason": null }, { - "name": "endgameImageLoad", + "name": "destination_ips", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "AutonomousSystemItem", + "description": "", + "fields": [ { - "name": "endgameNetwork", + "name": "name", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "endgameProcess", + "name": "number", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "TopNFlowItemDestination", + "description": "", + "fields": [ { - "name": "endgameRegistry", + "name": "autonomous_system", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "OBJECT", "name": "AutonomousSystemItem", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "endgameSecurity", + "name": "domain", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + } + }, "isDeprecated": false, "deprecationReason": null }, { - "name": "filebeatSystemModule", + "name": "ip", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "winlogbeatSecurity", + "name": "location", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "OBJECT", "name": "GeoItem", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "winlogbeatMWSysmonOperational", + "name": "flows", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, @@ -9431,10 +8209,10 @@ "deprecationReason": null }, { - "name": "inspect", + "name": "source_ips", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, + "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -9446,7 +8224,7 @@ }, { "kind": "INPUT_OBJECT", - "name": "TlsSortField", + "name": "NetworkDnsSortField", "description": "", "fields": null, "inputFields": [ @@ -9456,7 +8234,7 @@ "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "ENUM", "name": "TlsFields", "ofType": null } + "ofType": { "kind": "ENUM", "name": "NetworkDnsFields", "ofType": null } }, "defaultValue": null }, @@ -9477,19 +8255,48 @@ }, { "kind": "ENUM", - "name": "TlsFields", + "name": "NetworkDnsFields", "description": "", "fields": null, "inputFields": null, "interfaces": null, "enumValues": [ - { "name": "_id", "description": "", "isDeprecated": false, "deprecationReason": null } + { + "name": "dnsName", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "queryCount", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "uniqueDomains", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "dnsBytesIn", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "dnsBytesOut", + "description": "", + "isDeprecated": false, + "deprecationReason": null + } ], "possibleTypes": null }, { "kind": "OBJECT", - "name": "TlsData", + "name": "NetworkDnsData", "description": "", "fields": [ { @@ -9505,7 +8312,7 @@ "ofType": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "TlsEdges", "ofType": null } + "ofType": { "kind": "OBJECT", "name": "NetworkDnsEdges", "ofType": null } } } }, @@ -9543,6 +8350,26 @@ "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "histogram", + "description": "", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "MatrixOverOrdinalHistogramData", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -9552,7 +8379,7 @@ }, { "kind": "OBJECT", - "name": "TlsEdges", + "name": "NetworkDnsEdges", "description": "", "fields": [ { @@ -9562,7 +8389,7 @@ "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "TlsNode", "ofType": null } + "ofType": { "kind": "OBJECT", "name": "NetworkDnsItem", "ofType": null } }, "isDeprecated": false, "deprecationReason": null @@ -9587,7 +8414,7 @@ }, { "kind": "OBJECT", - "name": "TlsNode", + "name": "NetworkDnsItem", "description": "", "fields": [ { @@ -9599,86 +8426,177 @@ "deprecationReason": null }, { - "name": "timestamp", + "name": "dnsBytesIn", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Date", "ofType": null }, + "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "dnsBytesOut", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "dnsName", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "queryCount", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "notAfter", + "name": "uniqueDomains", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "MatrixOverOrdinalHistogramData", + "description": "", + "fields": [ + { + "name": "x", "description": "", "args": [], "type": { - "kind": "LIST", + "kind": "NON_NULL", "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { - "name": "subjects", + "name": "y", "description": "", "args": [], "type": { - "kind": "LIST", + "kind": "NON_NULL", "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } + "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { - "name": "ja3", + "name": "g", "description": "", "args": [], "type": { - "kind": "LIST", + "kind": "NON_NULL", "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } }, "isDeprecated": false, "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "NetworkDsOverTimeData", + "description": "", + "fields": [ + { + "name": "inspect", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "issuers", + "name": "matrixHistogramData", "description": "", "args": [], "type": { - "kind": "LIST", + "kind": "NON_NULL", "name": null, "ofType": { - "kind": "NON_NULL", + "kind": "LIST", "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "MatrixOverTimeHistogramData", + "ofType": null + } + } } }, - "isDeprecated": false, - "deprecationReason": null + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "NetworkHttpSortField", + "description": "", + "fields": null, + "inputFields": [ + { + "name": "direction", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "ENUM", "name": "Direction", "ofType": null } + }, + "defaultValue": null } ], - "inputFields": null, - "interfaces": [], + "interfaces": null, "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", - "name": "UncommonProcessesData", + "name": "NetworkHttpData", "description": "", "fields": [ { @@ -9694,7 +8612,7 @@ "ofType": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "UncommonProcessesEdges", "ofType": null } + "ofType": { "kind": "OBJECT", "name": "NetworkHttpEdges", "ofType": null } } } }, @@ -9741,7 +8659,7 @@ }, { "kind": "OBJECT", - "name": "UncommonProcessesEdges", + "name": "NetworkHttpEdges", "description": "", "fields": [ { @@ -9751,7 +8669,7 @@ "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "UncommonProcessItem", "ofType": null } + "ofType": { "kind": "OBJECT", "name": "NetworkHttpItem", "ofType": null } }, "isDeprecated": false, "deprecationReason": null @@ -9776,47 +8694,55 @@ }, { "kind": "OBJECT", - "name": "UncommonProcessItem", + "name": "NetworkHttpItem", "description": "", "fields": [ { "name": "_id", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "instances", + "name": "domains", "description": "", "args": [], "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + } + } }, "isDeprecated": false, "deprecationReason": null }, { - "name": "process", + "name": "lastHost", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "ProcessEcsFields", "ofType": null } - }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "hosts", + "name": "lastSourceIp", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "methods", "description": "", "args": [], "type": { @@ -9828,7 +8754,7 @@ "ofType": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "OBJECT", "name": "HostEcsFields", "ofType": null } + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } } } }, @@ -9836,10 +8762,38 @@ "deprecationReason": null }, { - "name": "user", + "name": "path", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "UserEcsFields", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "requestCount", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "statuses", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + } + } + }, "isDeprecated": false, "deprecationReason": null } @@ -10029,6 +8983,22 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "indexNames", + "description": "", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "notes", "description": "", @@ -11496,6 +10466,20 @@ }, "defaultValue": null }, + { + "name": "indexNames", + "description": "", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + } + }, + "defaultValue": null + }, { "name": "title", "description": "", @@ -12777,6 +11761,16 @@ ], "possibleTypes": null }, + { + "kind": "SCALAR", + "name": "ToStringArrayNoNullable", + "description": "", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, { "kind": "OBJECT", "name": "EcsEdges", @@ -13111,6 +12105,143 @@ ], "possibleTypes": null }, + { + "kind": "SCALAR", + "name": "ToIFieldSubTypeNonNullable", + "description": "", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "IndexField", + "description": "A descriptor of a field in an index", + "fields": [ + { + "name": "category", + "description": "Where the field belong", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "example", + "description": "Example of field's value", + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "indexes", + "description": "whether the field's belong to an alias index", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": "The name of the field", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": "The type of the field's values as recognized by Kibana", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "searchable", + "description": "Whether the field's values can be efficiently searched for", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "aggregatable", + "description": "Whether the field's values can be aggregated", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": "Description of the field", + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "format", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "esTypes", + "description": "the elastic type as mapped in the index", + "args": [], + "type": { "kind": "SCALAR", "name": "ToStringArrayNoNullable", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "subType", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "ToIFieldSubTypeNonNullable", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "ENUM", "name": "FlowDirection", diff --git a/x-pack/plugins/security_solution/public/graphql/types.ts b/x-pack/plugins/security_solution/public/graphql/types.ts index 65d9212f77dcc..bcb580a1a2988 100644 --- a/x-pack/plugins/security_solution/public/graphql/types.ts +++ b/x-pack/plugins/security_solution/public/graphql/types.ts @@ -95,12 +95,6 @@ export interface NetworkHttpSortField { direction: Direction; } -export interface TlsSortField { - field: TlsFields; - - direction: Direction; -} - export interface PageInfoTimeline { pageIndex: number; @@ -138,6 +132,8 @@ export interface TimelineInput { kqlQuery?: Maybe; + indexNames?: Maybe; + title?: Maybe; templateTimelineId?: Maybe; @@ -354,10 +350,6 @@ export enum NetworkDnsFields { dnsBytesOut = 'dnsBytesOut', } -export enum TlsFields { - _id = '_id', -} - export enum DataProviderType { default = 'default', template = 'template', @@ -423,10 +415,6 @@ export enum FlowDirection { biDirectional = 'biDirectional', } -export type ToStringArrayNoNullable = any; - -export type ToIFieldSubTypeNonNullable = any; - export type ToStringArray = string[]; export type Date = string; @@ -441,6 +429,10 @@ export type ToAny = any; export type EsValue = any; +export type ToStringArrayNoNullable = any; + +export type ToIFieldSubTypeNonNullable = any; + // ==================================================== // Scalars // ==================================================== @@ -564,14 +556,6 @@ export interface Source { NetworkDnsHistogram: NetworkDsOverTimeData; NetworkHttp: NetworkHttpData; - - OverviewNetwork?: Maybe; - - OverviewHost?: Maybe; - - Tls: TlsData; - /** Gets UncommonProcesses based on a timerange, or all UncommonProcesses if no criteria is specified */ - UncommonProcesses: UncommonProcessesData; /** Just a simple example to get the app name */ whoAmI?: Maybe; } @@ -603,33 +587,7 @@ export interface SourceStatus { /** Whether the configured alias or wildcard pattern resolve to any auditbeat indices */ indicesExist: boolean; /** The list of fields defined in the index mappings */ - indexFields: IndexField[]; -} - -/** A descriptor of a field in an index */ -export interface IndexField { - /** Where the field belong */ - category: string; - /** Example of field's value */ - example?: Maybe; - /** whether the field's belong to an alias index */ - indexes: (Maybe)[]; - /** The name of the field */ - name: string; - /** The type of the field's values as recognized by Kibana */ - type: string; - /** Whether the field's values can be efficiently searched for */ - searchable: boolean; - /** Whether the field's values can be aggregated */ - aggregatable: boolean; - /** Description of the field */ - description?: Maybe; - - format?: Maybe; - /** the elastic type as mapped in the index */ - esTypes?: Maybe; - - subType?: Maybe; + indexFields: string[]; } export interface AuthenticationsData { @@ -1870,122 +1828,6 @@ export interface NetworkHttpItem { statuses: string[]; } -export interface OverviewNetworkData { - auditbeatSocket?: Maybe; - - filebeatCisco?: Maybe; - - filebeatNetflow?: Maybe; - - filebeatPanw?: Maybe; - - filebeatSuricata?: Maybe; - - filebeatZeek?: Maybe; - - packetbeatDNS?: Maybe; - - packetbeatFlow?: Maybe; - - packetbeatTLS?: Maybe; - - inspect?: Maybe; -} - -export interface OverviewHostData { - auditbeatAuditd?: Maybe; - - auditbeatFIM?: Maybe; - - auditbeatLogin?: Maybe; - - auditbeatPackage?: Maybe; - - auditbeatProcess?: Maybe; - - auditbeatUser?: Maybe; - - endgameDns?: Maybe; - - endgameFile?: Maybe; - - endgameImageLoad?: Maybe; - - endgameNetwork?: Maybe; - - endgameProcess?: Maybe; - - endgameRegistry?: Maybe; - - endgameSecurity?: Maybe; - - filebeatSystemModule?: Maybe; - - winlogbeatSecurity?: Maybe; - - winlogbeatMWSysmonOperational?: Maybe; - - inspect?: Maybe; -} - -export interface TlsData { - edges: TlsEdges[]; - - totalCount: number; - - pageInfo: PageInfoPaginated; - - inspect?: Maybe; -} - -export interface TlsEdges { - node: TlsNode; - - cursor: CursorType; -} - -export interface TlsNode { - _id?: Maybe; - - timestamp?: Maybe; - - notAfter?: Maybe; - - subjects?: Maybe; - - ja3?: Maybe; - - issuers?: Maybe; -} - -export interface UncommonProcessesData { - edges: UncommonProcessesEdges[]; - - totalCount: number; - - pageInfo: PageInfoPaginated; - - inspect?: Maybe; -} - -export interface UncommonProcessesEdges { - node: UncommonProcessItem; - - cursor: CursorType; -} - -export interface UncommonProcessItem { - _id: string; - - instances: number; - - process: ProcessEcsFields; - - hosts: HostEcsFields[]; - - user?: Maybe; -} - export interface SayMyName { /** The id of the source */ appName: string; @@ -2018,6 +1860,8 @@ export interface TimelineResult { kqlQuery?: Maybe; + indexNames?: Maybe; + notes?: Maybe; noteIds?: Maybe; @@ -2290,6 +2134,32 @@ export interface HostFields { type?: Maybe; } +/** A descriptor of a field in an index */ +export interface IndexField { + /** Where the field belong */ + category: string; + /** Example of field's value */ + example?: Maybe; + /** whether the field's belong to an alias index */ + indexes: (Maybe)[]; + /** The name of the field */ + name: string; + /** The type of the field's values as recognized by Kibana */ + type: string; + /** Whether the field's values can be efficiently searched for */ + searchable: boolean; + /** Whether the field's values can be aggregated */ + aggregatable: boolean; + /** Description of the field */ + description?: Maybe; + + format?: Maybe; + /** the elastic type as mapped in the index */ + esTypes?: Maybe; + + subType?: Maybe; +} + // ==================================================== // Arguments // ==================================================== @@ -2555,50 +2425,6 @@ export interface NetworkHttpSourceArgs { defaultIndex: string[]; } -export interface OverviewNetworkSourceArgs { - id?: Maybe; - - timerange: TimerangeInput; - - filterQuery?: Maybe; - - defaultIndex: string[]; -} -export interface OverviewHostSourceArgs { - id?: Maybe; - - timerange: TimerangeInput; - - filterQuery?: Maybe; - - defaultIndex: string[]; -} -export interface TlsSourceArgs { - filterQuery?: Maybe; - - id?: Maybe; - - ip: string; - - pagination: PaginationInputPaginated; - - sort: TlsSortField; - - flowTarget: FlowTargetSourceDest; - - timerange: TimerangeInput; - - defaultIndex: string[]; -} -export interface UncommonProcessesSourceArgs { - timerange: TimerangeInput; - - pagination: PaginationInputPaginated; - - filterQuery?: Maybe; - - defaultIndex: string[]; -} export interface IndicesExistSourceStatusArgs { defaultIndex: string[]; } @@ -2735,61 +2561,6 @@ export namespace GetMatrixHistogramQuery { }; } -export namespace SourceQuery { - export type Variables = { - sourceId?: Maybe; - defaultIndex: string[]; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - status: Status; - }; - - export type Status = { - __typename?: 'SourceStatus'; - - indicesExist: boolean; - - indexFields: IndexFields[]; - }; - - export type IndexFields = { - __typename?: 'IndexField'; - - category: string; - - description: Maybe; - - example: Maybe; - - indexes: (Maybe)[]; - - name: string; - - searchable: boolean; - - type: string; - - aggregatable: boolean; - - format: Maybe; - - esTypes: Maybe; - - subType: Maybe; - }; -} - export namespace GetAuthenticationsQuery { export type Variables = { sourceId: string; @@ -2930,12 +2701,13 @@ export namespace GetAuthenticationsQuery { }; } -export namespace GetHostFirstLastSeenQuery { +export namespace GetHostOverviewQuery { export type Variables = { sourceId: string; hostName: string; + timerange: TimerangeInput; defaultIndex: string[]; - docValueFields: DocValueFieldsInput[]; + inspect: boolean; }; export type Query = { @@ -2949,124 +2721,137 @@ export namespace GetHostFirstLastSeenQuery { id: string; - HostFirstLastSeen: HostFirstLastSeen; + HostOverview: HostOverview; }; - export type HostFirstLastSeen = { - __typename?: 'FirstLastSeenHost'; + export type HostOverview = { + __typename?: 'HostItem'; - firstSeen: Maybe; + _id: Maybe; - lastSeen: Maybe; - }; -} + host: Maybe; -export namespace GetHostsTableQuery { - export type Variables = { - sourceId: string; - timerange: TimerangeInput; - pagination: PaginationInputPaginated; - sort: HostsSortField; - filterQuery?: Maybe; - defaultIndex: string[]; - inspect: boolean; - docValueFields: DocValueFieldsInput[]; - }; + cloud: Maybe; - export type Query = { - __typename?: 'Query'; + inspect: Maybe; - source: Source; + endpoint: Maybe; }; - export type Source = { - __typename?: 'Source'; - - id: string; + export type Host = { + __typename?: 'HostEcsFields'; - Hosts: Hosts; - }; + architecture: Maybe; - export type Hosts = { - __typename?: 'HostsData'; + id: Maybe; - totalCount: number; + ip: Maybe; - edges: Edges[]; + mac: Maybe; - pageInfo: PageInfo; + name: Maybe; - inspect: Maybe; + os: Maybe; + + type: Maybe; }; - export type Edges = { - __typename?: 'HostsEdges'; + export type Os = { + __typename?: 'OsEcsFields'; - node: Node; + family: Maybe; - cursor: Cursor; + name: Maybe; + + platform: Maybe; + + version: Maybe; }; - export type Node = { - __typename?: 'HostItem'; + export type Cloud = { + __typename?: 'CloudFields'; - _id: Maybe; + instance: Maybe; - lastSeen: Maybe; + machine: Maybe; - host: Maybe; + provider: Maybe<(Maybe)[]>; + + region: Maybe<(Maybe)[]>; }; - export type Host = { - __typename?: 'HostEcsFields'; + export type Instance = { + __typename?: 'CloudInstance'; - id: Maybe; + id: Maybe<(Maybe)[]>; + }; - name: Maybe; + export type Machine = { + __typename?: 'CloudMachine'; - os: Maybe; + type: Maybe<(Maybe)[]>; }; - export type Os = { - __typename?: 'OsEcsFields'; + export type Inspect = { + __typename?: 'Inspect'; - name: Maybe; + dsl: string[]; - version: Maybe; + response: string[]; }; - export type Cursor = { - __typename?: 'CursorType'; + export type Endpoint = { + __typename?: 'EndpointFields'; - value: Maybe; + endpointPolicy: Maybe; + + policyStatus: Maybe; + + sensorVersion: Maybe; }; +} - export type PageInfo = { - __typename?: 'PageInfoPaginated'; +export namespace GetHostFirstLastSeenQuery { + export type Variables = { + sourceId: string; + hostName: string; + defaultIndex: string[]; + docValueFields: DocValueFieldsInput[]; + }; - activePage: number; + export type Query = { + __typename?: 'Query'; - fakeTotalCount: number; + source: Source; + }; - showMorePagesIndicator: boolean; + export type Source = { + __typename?: 'Source'; + + id: string; + + HostFirstLastSeen: HostFirstLastSeen; }; - export type Inspect = { - __typename?: 'Inspect'; + export type HostFirstLastSeen = { + __typename?: 'FirstLastSeenHost'; - dsl: string[]; + firstSeen: Maybe; - response: string[]; + lastSeen: Maybe; }; } -export namespace GetHostOverviewQuery { +export namespace GetHostsTableQuery { export type Variables = { sourceId: string; - hostName: string; timerange: TimerangeInput; + pagination: PaginationInputPaginated; + sort: HostsSortField; + filterQuery?: Maybe; defaultIndex: string[]; inspect: boolean; + docValueFields: DocValueFieldsInput[]; }; export type Query = { @@ -3080,75 +2865,71 @@ export namespace GetHostOverviewQuery { id: string; - HostOverview: HostOverview; + Hosts: Hosts; }; - export type HostOverview = { - __typename?: 'HostItem'; + export type Hosts = { + __typename?: 'HostsData'; - _id: Maybe; + totalCount: number; - host: Maybe; + edges: Edges[]; - cloud: Maybe; + pageInfo: PageInfo; inspect: Maybe; + }; - endpoint: Maybe; + export type Edges = { + __typename?: 'HostsEdges'; + + node: Node; + + cursor: Cursor; }; - export type Host = { - __typename?: 'HostEcsFields'; + export type Node = { + __typename?: 'HostItem'; - architecture: Maybe; + _id: Maybe; - id: Maybe; + lastSeen: Maybe; - ip: Maybe; + host: Maybe; + }; - mac: Maybe; + export type Host = { + __typename?: 'HostEcsFields'; + + id: Maybe; name: Maybe; os: Maybe; - - type: Maybe; }; export type Os = { __typename?: 'OsEcsFields'; - family: Maybe; - name: Maybe; - platform: Maybe; - version: Maybe; }; - export type Cloud = { - __typename?: 'CloudFields'; - - instance: Maybe; - - machine: Maybe; - - provider: Maybe<(Maybe)[]>; + export type Cursor = { + __typename?: 'CursorType'; - region: Maybe<(Maybe)[]>; + value: Maybe; }; - export type Instance = { - __typename?: 'CloudInstance'; + export type PageInfo = { + __typename?: 'PageInfoPaginated'; - id: Maybe<(Maybe)[]>; - }; + activePage: number; - export type Machine = { - __typename?: 'CloudMachine'; + fakeTotalCount: number; - type: Maybe<(Maybe)[]>; + showMorePagesIndicator: boolean; }; export type Inspect = { @@ -3158,16 +2939,6 @@ export namespace GetHostOverviewQuery { response: string[]; }; - - export type Endpoint = { - __typename?: 'EndpointFields'; - - endpointPolicy: Maybe; - - policyStatus: Maybe; - - sensorVersion: Maybe; - }; } export namespace GetKpiHostDetailsQuery { @@ -3300,111 +3071,6 @@ export namespace GetKpiHostsQuery { }; } -export namespace GetUncommonProcessesQuery { - export type Variables = { - sourceId: string; - timerange: TimerangeInput; - pagination: PaginationInputPaginated; - filterQuery?: Maybe; - defaultIndex: string[]; - inspect: boolean; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - UncommonProcesses: UncommonProcesses; - }; - - export type UncommonProcesses = { - __typename?: 'UncommonProcessesData'; - - totalCount: number; - - edges: Edges[]; - - pageInfo: PageInfo; - - inspect: Maybe; - }; - - export type Edges = { - __typename?: 'UncommonProcessesEdges'; - - node: Node; - - cursor: Cursor; - }; - - export type Node = { - __typename?: 'UncommonProcessItem'; - - _id: string; - - instances: number; - - process: Process; - - user: Maybe; - - hosts: Hosts[]; - }; - - export type Process = { - __typename?: 'ProcessEcsFields'; - - args: Maybe; - - name: Maybe; - }; - - export type User = { - __typename?: 'UserEcsFields'; - - id: Maybe; - - name: Maybe; - }; - - export type Hosts = { - __typename?: 'HostEcsFields'; - - name: Maybe; - }; - - export type Cursor = { - __typename?: 'CursorType'; - - value: Maybe; - }; - - export type PageInfo = { - __typename?: 'PageInfoPaginated'; - - activePage: number; - - fakeTotalCount: number; - - showMorePagesIndicator: boolean; - }; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; -} - export namespace GetIpOverviewQuery { export type Variables = { sourceId: string; @@ -4119,92 +3785,6 @@ export namespace GetNetworkTopNFlowQuery { }; } -export namespace GetTlsQuery { - export type Variables = { - sourceId: string; - filterQuery?: Maybe; - flowTarget: FlowTargetSourceDest; - ip: string; - pagination: PaginationInputPaginated; - sort: TlsSortField; - timerange: TimerangeInput; - defaultIndex: string[]; - inspect: boolean; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - Tls: Tls; - }; - - export type Tls = { - __typename?: 'TlsData'; - - totalCount: number; - - edges: Edges[]; - - pageInfo: PageInfo; - - inspect: Maybe; - }; - - export type Edges = { - __typename?: 'TlsEdges'; - - node: Node; - - cursor: Cursor; - }; - - export type Node = { - __typename?: 'TlsNode'; - - _id: Maybe; - - subjects: Maybe; - - ja3: Maybe; - - issuers: Maybe; - - notAfter: Maybe; - }; - - export type Cursor = { - __typename?: 'CursorType'; - - value: Maybe; - }; - - export type PageInfo = { - __typename?: 'PageInfoPaginated'; - - activePage: number; - - fakeTotalCount: number; - - showMorePagesIndicator: boolean; - }; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; -} - export namespace GetUsersQuery { export type Variables = { sourceId: string; @@ -4297,132 +3877,6 @@ export namespace GetUsersQuery { }; } -export namespace GetOverviewHostQuery { - export type Variables = { - sourceId: string; - timerange: TimerangeInput; - filterQuery?: Maybe; - defaultIndex: string[]; - inspect: boolean; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - OverviewHost: Maybe; - }; - - export type OverviewHost = { - __typename?: 'OverviewHostData'; - - auditbeatAuditd: Maybe; - - auditbeatFIM: Maybe; - - auditbeatLogin: Maybe; - - auditbeatPackage: Maybe; - - auditbeatProcess: Maybe; - - auditbeatUser: Maybe; - - endgameDns: Maybe; - - endgameFile: Maybe; - - endgameImageLoad: Maybe; - - endgameNetwork: Maybe; - - endgameProcess: Maybe; - - endgameRegistry: Maybe; - - endgameSecurity: Maybe; - - filebeatSystemModule: Maybe; - - winlogbeatSecurity: Maybe; - - winlogbeatMWSysmonOperational: Maybe; - - inspect: Maybe; - }; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; -} - -export namespace GetOverviewNetworkQuery { - export type Variables = { - sourceId: string; - timerange: TimerangeInput; - filterQuery?: Maybe; - defaultIndex: string[]; - inspect: boolean; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - OverviewNetwork: Maybe; - }; - - export type OverviewNetwork = { - __typename?: 'OverviewNetworkData'; - - auditbeatSocket: Maybe; - - filebeatCisco: Maybe; - - filebeatNetflow: Maybe; - - filebeatPanw: Maybe; - - filebeatSuricata: Maybe; - - filebeatZeek: Maybe; - - packetbeatDNS: Maybe; - - packetbeatFlow: Maybe; - - packetbeatTLS: Maybe; - - inspect: Maybe; - }; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; -} - export namespace GetAllTimeline { export type Variables = { pageInfo: PageInfoTimeline; @@ -5558,6 +5012,8 @@ export namespace GetOneTimeline { kqlQuery: Maybe; + indexNames: Maybe; + notes: Maybe; noteIds: Maybe; @@ -5890,6 +5346,8 @@ export namespace PersistTimelineMutation { kqlQuery: Maybe; + indexNames: Maybe; + title: Maybe; dateRange: Maybe; diff --git a/x-pack/plugins/security_solution/public/hosts/components/authentications_table/translations.ts b/x-pack/plugins/security_solution/public/hosts/components/authentications_table/translations.ts index 0e918275e2b18..d437a6b73f71a 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/authentications_table/translations.ts +++ b/x-pack/plugins/security_solution/public/hosts/components/authentications_table/translations.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; export const AUTHENTICATIONS = i18n.translate( - 'xpack.securitySolution.authenticationsTable.authenticationFailures', + 'xpack.securitySolution.authenticationsTable.authentications', { defaultMessage: 'Authentications', } diff --git a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.test.tsx b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.test.tsx index 606b43c6508fb..4f64cca45d162 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.test.tsx @@ -40,7 +40,12 @@ describe('FirstLastSeen Component', () => { useFirstLastSeenHostMock.mockReturnValue([true, MOCKED_RESPONSE]); const { container } = render( - + ); expect(container.innerHTML).toBe( @@ -52,7 +57,12 @@ describe('FirstLastSeen Component', () => { useFirstLastSeenHostMock.mockReturnValue([false, MOCKED_RESPONSE]); const { container } = render( - + ); @@ -69,7 +79,12 @@ describe('FirstLastSeen Component', () => { useFirstLastSeenHostMock.mockReturnValue([false, MOCKED_RESPONSE]); const { container } = render( - + ); await act(() => @@ -91,7 +106,12 @@ describe('FirstLastSeen Component', () => { ]); const { container } = render( - + ); @@ -114,7 +134,12 @@ describe('FirstLastSeen Component', () => { ]); const { container } = render( - + ); @@ -137,7 +162,12 @@ describe('FirstLastSeen Component', () => { ]); const { container } = render( - + ); await act(() => @@ -157,7 +187,12 @@ describe('FirstLastSeen Component', () => { ]); const { container } = render( - + ); await act(() => diff --git a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx index a1b72fb39069c..ee415560cf9de 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx @@ -10,6 +10,7 @@ import React, { useMemo } from 'react'; import { useFirstLastSeenHost } from '../../containers/hosts/first_last_seen'; import { getEmptyTagValue } from '../../../common/components/empty_value'; import { FormattedRelativePreferenceDate } from '../../../common/components/formatted_date'; +import { DocValueFields } from '../../../../common/search_strategy'; export enum FirstLastSeenHostType { FIRST_SEEN = 'first-seen', @@ -17,47 +18,53 @@ export enum FirstLastSeenHostType { } interface FirstLastSeenHostProps { + docValueFields: DocValueFields[]; hostName: string; + indexNames: string[]; type: FirstLastSeenHostType; } -export const FirstLastSeenHost = React.memo(({ hostName, type }) => { - const [loading, { firstSeen, lastSeen, errorMessage }] = useFirstLastSeenHost({ - hostName, - }); - const valueSeen = useMemo( - () => (type === FirstLastSeenHostType.FIRST_SEEN ? firstSeen : lastSeen), - [firstSeen, lastSeen, type] - ); +export const FirstLastSeenHost = React.memo( + ({ docValueFields, hostName, type, indexNames }) => { + const [loading, { firstSeen, lastSeen, errorMessage }] = useFirstLastSeenHost({ + docValueFields, + hostName, + indexNames, + }); + const valueSeen = useMemo( + () => (type === FirstLastSeenHostType.FIRST_SEEN ? firstSeen : lastSeen), + [firstSeen, lastSeen, type] + ); + + if (errorMessage != null) { + return ( + + + + ); + } - if (errorMessage != null) { return ( - - - + <> + {loading && } + {!loading && valueSeen != null && new Date(valueSeen).toString() === 'Invalid Date' + ? valueSeen + : !loading && + valueSeen != null && ( + + + + )} + {!loading && valueSeen == null && getEmptyTagValue()} + ); } - - return ( - <> - {loading && } - {!loading && valueSeen != null && new Date(valueSeen).toString() === 'Invalid Date' - ? valueSeen - : !loading && - valueSeen != null && ( - - - - )} - {!loading && valueSeen == null && getEmptyTagValue()} - - ); -}); +); FirstLastSeenHost.displayName = 'FirstLastSeenHost'; diff --git a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/__snapshots__/index.test.tsx.snap index 3143e680913b2..1d70f4f72ac8b 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/__snapshots__/index.test.tsx.snap @@ -68,97 +68,6 @@ exports[`Hosts Table rendering it renders the default Hosts table 1`] = ` } fakeTotalCount={50} id="hostsQuery" - indexPattern={ - Object { - "fields": Array [ - Object { - "aggregatable": true, - "name": "@timestamp", - "searchable": true, - "type": "date", - }, - Object { - "aggregatable": true, - "name": "@version", - "searchable": true, - "type": "string", - }, - Object { - "aggregatable": true, - "name": "agent.ephemeral_id", - "searchable": true, - "type": "string", - }, - Object { - "aggregatable": true, - "name": "agent.hostname", - "searchable": true, - "type": "string", - }, - Object { - "aggregatable": true, - "name": "agent.id", - "searchable": true, - "type": "string", - }, - Object { - "aggregatable": true, - "name": "agent.test1", - "searchable": true, - "type": "string", - }, - Object { - "aggregatable": true, - "name": "agent.test2", - "searchable": true, - "type": "string", - }, - Object { - "aggregatable": true, - "name": "agent.test3", - "searchable": true, - "type": "string", - }, - Object { - "aggregatable": true, - "name": "agent.test4", - "searchable": true, - "type": "string", - }, - Object { - "aggregatable": true, - "name": "agent.test5", - "searchable": true, - "type": "string", - }, - Object { - "aggregatable": true, - "name": "agent.test6", - "searchable": true, - "type": "string", - }, - Object { - "aggregatable": true, - "name": "agent.test7", - "searchable": true, - "type": "string", - }, - Object { - "aggregatable": true, - "name": "agent.test8", - "searchable": true, - "type": "string", - }, - Object { - "aggregatable": true, - "name": "host.name", - "searchable": true, - "type": "string", - }, - ], - "title": "filebeat-*,auditbeat-*,packetbeat-*", - } - } isInspect={false} loadPage={[MockFunction]} loading={false} diff --git a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx index c4a391687843c..29e4dc48ae3c7 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx @@ -12,7 +12,6 @@ import { MockedProvider } from 'react-apollo/test-utils'; import '../../../common/mock/match_media'; import { apolloClientObservable, - mockIndexPattern, mockGlobalState, TestProviders, SUB_PLUGINS_REDUCER, @@ -69,7 +68,6 @@ describe('Hosts Table', () => { data={mockData.Hosts.edges} id="hostsQuery" isInspect={false} - indexPattern={mockIndexPattern} fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.Hosts.pageInfo)} loading={false} loadPage={loadPage} @@ -92,7 +90,6 @@ describe('Hosts Table', () => { void; @@ -77,7 +75,6 @@ const HostsTableComponent = React.memo( direction, fakeTotalCount, id, - indexPattern, isInspect, limit, loading, diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/__snapshots__/index.test.tsx.snap deleted file mode 100644 index 2b2a35945bdf1..0000000000000 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,155 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`kpiHostsComponent render it should render KpiHostDetailsData 1`] = ` - - - - -`; - -exports[`kpiHostsComponent render it should render KpiHostsData 1`] = ` - - - - - -`; - -exports[`kpiHostsComponent render it should render spinner if it is loading 1`] = ` - - - - - -`; diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.tsx new file mode 100644 index 0000000000000..84003e5dea5e9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.tsx @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { StatItems } from '../../../../common/components/stat_items'; +import { useHostsKpiAuthentications } from '../../../containers/kpi_hosts/authentications'; +import { HostsKpiBaseComponentManage } from '../common'; +import { HostsKpiProps, HostsKpiChartColors } from '../types'; +import * as i18n from './translations'; + +export const fieldsMapping: Readonly = [ + { + key: 'authentication', + fields: [ + { + key: 'authenticationsSuccess', + name: i18n.SUCCESS_CHART_LABEL, + description: i18n.SUCCESS_UNIT_LABEL, + value: null, + color: HostsKpiChartColors.authenticationsSuccess, + icon: 'check', + }, + { + key: 'authenticationsFailure', + name: i18n.FAIL_CHART_LABEL, + description: i18n.FAIL_UNIT_LABEL, + value: null, + color: HostsKpiChartColors.authenticationsFailure, + icon: 'cross', + }, + ], + enableAreaChart: true, + enableBarChart: true, + description: i18n.USER_AUTHENTICATIONS, + }, +]; + +const HostsKpiAuthenticationsComponent: React.FC = ({ + filterQuery, + from, + indexNames, + to, + narrowDateRange, + setQuery, + skip, +}) => { + const [loading, { refetch, id, inspect, ...data }] = useHostsKpiAuthentications({ + filterQuery, + endDate: to, + indexNames, + startDate: from, + skip, + }); + + return ( + + ); +}; + +export const HostsKpiAuthentications = React.memo(HostsKpiAuthenticationsComponent); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/translations.ts b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/translations.ts similarity index 55% rename from x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/translations.ts rename to x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/translations.ts index 82543e6f106fa..5175781159c91 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/translations.ts +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/translations.ts @@ -3,11 +3,8 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { i18n } from '@kbn/i18n'; -export const HOSTS = i18n.translate('xpack.securitySolution.kpiHosts.hosts.title', { - defaultMessage: 'Hosts', -}); +import { i18n } from '@kbn/i18n'; export const USER_AUTHENTICATIONS = i18n.translate( 'xpack.securitySolution.kpiHosts.userAuthentications.title', @@ -43,35 +40,3 @@ export const FAIL_CHART_LABEL = i18n.translate( defaultMessage: 'Fail', } ); - -export const UNIQUE_IPS = i18n.translate('xpack.securitySolution.kpiHosts.uniqueIps.title', { - defaultMessage: 'Unique IPs', -}); - -export const SOURCE_UNIT_LABEL = i18n.translate( - 'xpack.securitySolution.kpiHosts.uniqueIps.sourceUnitLabel', - { - defaultMessage: 'source', - } -); - -export const DESTINATION_UNIT_LABEL = i18n.translate( - 'xpack.securitySolution.kpiHosts.uniqueIps.destinationUnitLabel', - { - defaultMessage: 'destination', - } -); - -export const SOURCE_CHART_LABEL = i18n.translate( - 'xpack.securitySolution.kpiHosts.uniqueIps.sourceChartLabel', - { - defaultMessage: 'Src.', - } -); - -export const DESTINATION_CHART_LABEL = i18n.translate( - 'xpack.securitySolution.kpiHosts.uniqueIps.destinationChartLabel', - { - defaultMessage: 'Dest.', - } -); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/common/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/common/index.tsx new file mode 100644 index 0000000000000..7c51a503092af --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/common/index.tsx @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { EuiFlexItem, EuiLoadingSpinner, EuiFlexGroup } from '@elastic/eui'; +import styled from 'styled-components'; + +import { manageQuery } from '../../../../common/components/page/manage_query'; +import { HostsKpiStrategyResponse } from '../../../../../common/search_strategy'; +import { + StatItemsComponent, + StatItemsProps, + useKpiMatrixStatus, + StatItems, +} from '../../../../common/components/stat_items'; +import { UpdateDateRange } from '../../../../common/components/charts/common'; + +const kpiWidgetHeight = 247; + +export const FlexGroup = styled(EuiFlexGroup)` + min-height: ${kpiWidgetHeight}px; +`; + +FlexGroup.displayName = 'FlexGroup'; + +export const HostsKpiBaseComponent = React.memo<{ + fieldsMapping: Readonly; + data: HostsKpiStrategyResponse; + loading?: boolean; + id: string; + from: string; + to: string; + narrowDateRange: UpdateDateRange; +}>(({ fieldsMapping, data, id, loading = false, from, to, narrowDateRange }) => { + const statItemsProps: StatItemsProps[] = useKpiMatrixStatus( + fieldsMapping, + data, + id, + from, + to, + narrowDateRange + ); + + if (loading) { + return ( + + + + + + ); + } + + return ( + + {statItemsProps.map((mappedStatItemProps) => ( + + ))} + + ); +}); + +HostsKpiBaseComponent.displayName = 'HostsKpiBaseComponent'; + +export const HostsKpiBaseComponentManage = manageQuery(HostsKpiBaseComponent); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/index.tsx new file mode 100644 index 0000000000000..908ff717e2711 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/index.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { StatItems } from '../../../../common/components/stat_items'; +import { useHostsKpiHosts } from '../../../containers/kpi_hosts/hosts'; +import { HostsKpiBaseComponentManage } from '../common'; +import { HostsKpiProps, HostsKpiChartColors } from '../types'; +import * as i18n from './translations'; + +export const fieldsMapping: Readonly = [ + { + key: 'hosts', + fields: [ + { + key: 'hosts', + value: null, + color: HostsKpiChartColors.hosts, + icon: 'storage', + }, + ], + enableAreaChart: true, + description: i18n.HOSTS, + }, +]; + +const HostsKpiHostsComponent: React.FC = ({ + filterQuery, + from, + indexNames, + to, + narrowDateRange, + setQuery, + skip, +}) => { + const [loading, { refetch, id, inspect, ...data }] = useHostsKpiHosts({ + filterQuery, + endDate: to, + indexNames, + startDate: from, + skip, + }); + + return ( + + ); +}; + +export const HostsKpiHosts = React.memo(HostsKpiHostsComponent); diff --git a/x-pack/index.js b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/translations.ts similarity index 61% rename from x-pack/index.js rename to x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/translations.ts index cb68004c26d65..7754591ab415b 100644 --- a/x-pack/index.js +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/translations.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { xpackMain } from './legacy/plugins/xpack_main'; +import { i18n } from '@kbn/i18n'; -module.exports = function (kibana) { - return [xpackMain(kibana)]; -}; +export const HOSTS = i18n.translate('xpack.securitySolution.kpiHosts.hosts.title', { + defaultMessage: 'Hosts', +}); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.test.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.test.tsx deleted file mode 100644 index 7731881df6d2c..0000000000000 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.test.tsx +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { mockKpiHostsData, mockKpiHostDetailsData } from './mock'; -import React from 'react'; -import { shallow, ShallowWrapper } from 'enzyme'; -import '../../../common/mock/match_media'; -import { KpiHostsComponentBase } from '.'; -import * as statItems from '../../../common/components/stat_items'; -import { kpiHostsMapping } from './kpi_hosts_mapping'; -import { kpiHostDetailsMapping } from './kpi_host_details_mapping'; - -describe('kpiHostsComponent', () => { - const ID = 'kpiHost'; - const from = '2019-06-15T06:00:00.000Z'; - const to = '2019-06-18T06:00:00.000Z'; - const narrowDateRange = () => {}; - describe('render', () => { - test('it should render spinner if it is loading', () => { - const wrapper: ShallowWrapper = shallow( - - ); - expect(wrapper).toMatchSnapshot(); - }); - - test('it should render KpiHostsData', () => { - const wrapper: ShallowWrapper = shallow( - - ); - expect(wrapper).toMatchSnapshot(); - }); - - test('it should render KpiHostDetailsData', () => { - const wrapper: ShallowWrapper = shallow( - - ); - expect(wrapper).toMatchSnapshot(); - }); - }); - - const table = [ - [mockKpiHostsData, kpiHostsMapping] as [typeof mockKpiHostsData, typeof kpiHostsMapping], - [mockKpiHostDetailsData, kpiHostDetailsMapping] as [ - typeof mockKpiHostDetailsData, - typeof kpiHostDetailsMapping - ], - ]; - - describe.each(table)( - 'it should handle KpiHostsProps and KpiHostDetailsProps', - (data, mapping) => { - let mockUseKpiMatrixStatus: jest.SpyInstance; - beforeAll(() => { - mockUseKpiMatrixStatus = jest.spyOn(statItems, 'useKpiMatrixStatus'); - }); - - beforeEach(() => { - shallow( - - ); - }); - - afterEach(() => { - mockUseKpiMatrixStatus.mockClear(); - }); - - afterAll(() => { - mockUseKpiMatrixStatus.mockRestore(); - }); - - test(`it should apply correct mapping by given data type`, () => { - expect(mockUseKpiMatrixStatus).toBeCalledWith(mapping, data, ID, from, to, narrowDateRange); - }); - } - ); -}); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx index c39e86591013f..6174e174db5a6 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx @@ -4,81 +4,83 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; import React from 'react'; -import styled from 'styled-components'; +import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; -import { KpiHostsData, KpiHostDetailsData } from '../../../graphql/types'; -import { - StatItemsComponent, - StatItemsProps, - useKpiMatrixStatus, -} from '../../../common/components/stat_items'; -import { kpiHostsMapping } from './kpi_hosts_mapping'; -import { kpiHostDetailsMapping } from './kpi_host_details_mapping'; -import { UpdateDateRange } from '../../../common/components/charts/common'; +import { HostsKpiAuthentications } from './authentications'; +import { HostsKpiHosts } from './hosts'; +import { HostsKpiUniqueIps } from './unique_ips'; +import { HostsKpiProps } from './types'; -const kpiWidgetHeight = 247; - -interface GenericKpiHostProps { - from: string; - id: string; - loading: boolean; - to: string; - narrowDateRange: UpdateDateRange; -} - -interface KpiHostsProps extends GenericKpiHostProps { - data: KpiHostsData; -} - -interface KpiHostDetailsProps extends GenericKpiHostProps { - data: KpiHostDetailsData; -} - -const FlexGroupSpinner = styled(EuiFlexGroup)` - { - min-height: ${kpiWidgetHeight}px; - } -`; - -FlexGroupSpinner.displayName = 'FlexGroupSpinner'; - -export const KpiHostsComponentBase = ({ - data, - from, - loading, - id, - to, - narrowDateRange, -}: KpiHostsProps | KpiHostDetailsProps) => { - const mappings = - (data as KpiHostsData).hosts !== undefined ? kpiHostsMapping : kpiHostDetailsMapping; - const statItemsProps: StatItemsProps[] = useKpiMatrixStatus( - mappings, - data, - id, - from, - to, - narrowDateRange - ); - return loading ? ( - - - +export const HostsKpiComponent = React.memo( + ({ filterQuery, from, indexNames, to, setQuery, skip, narrowDateRange }) => ( + + + + + + + + + - - ) : ( - - {statItemsProps.map((mappedStatItemProps, idx) => { - return ; - })} - ); -}; + ) +); -KpiHostsComponentBase.displayName = 'KpiHostsComponentBase'; +HostsKpiComponent.displayName = 'HostsKpiComponent'; -export const KpiHostsComponent = React.memo(KpiHostsComponentBase); +export const HostsDetailsKpiComponent = React.memo( + ({ filterQuery, from, indexNames, to, setQuery, skip, narrowDateRange }) => ( + + + + + + + + + ) +); -KpiHostsComponent.displayName = 'KpiHostsComponent'; +HostsDetailsKpiComponent.displayName = 'HostsDetailsKpiComponent'; diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/kpi_host_details_mapping.ts b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/kpi_host_details_mapping.ts deleted file mode 100644 index b3e98b70c4cb0..0000000000000 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/kpi_host_details_mapping.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import * as i18n from './translations'; -import { StatItems } from '../../../common/components/stat_items'; -import { KpiHostsChartColors } from './types'; - -export const kpiHostDetailsMapping: Readonly = [ - { - key: 'authentication', - index: 0, - fields: [ - { - key: 'authSuccess', - name: i18n.SUCCESS_CHART_LABEL, - description: i18n.SUCCESS_UNIT_LABEL, - value: null, - color: KpiHostsChartColors.authSuccess, - icon: 'check', - }, - { - key: 'authFailure', - name: i18n.FAIL_CHART_LABEL, - description: i18n.FAIL_UNIT_LABEL, - value: null, - color: KpiHostsChartColors.authFailure, - icon: 'cross', - }, - ], - enableAreaChart: true, - enableBarChart: true, - grow: 1, - description: i18n.USER_AUTHENTICATIONS, - }, - { - key: 'uniqueIps', - index: 1, - fields: [ - { - key: 'uniqueSourceIps', - name: i18n.SOURCE_CHART_LABEL, - description: i18n.SOURCE_UNIT_LABEL, - value: null, - color: KpiHostsChartColors.uniqueSourceIps, - icon: 'visMapCoordinate', - }, - { - key: 'uniqueDestinationIps', - name: i18n.DESTINATION_CHART_LABEL, - description: i18n.DESTINATION_UNIT_LABEL, - value: null, - color: KpiHostsChartColors.uniqueDestinationIps, - icon: 'visMapCoordinate', - }, - ], - enableAreaChart: true, - enableBarChart: true, - grow: 1, - description: i18n.UNIQUE_IPS, - }, -]; diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/kpi_hosts_mapping.ts b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/kpi_hosts_mapping.ts deleted file mode 100644 index 78a9fd5b84d1f..0000000000000 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/kpi_hosts_mapping.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import * as i18n from './translations'; -import { KpiHostsChartColors } from './types'; -import { StatItems } from '../../../common/components/stat_items'; - -export const kpiHostsMapping: Readonly = [ - { - key: 'hosts', - index: 0, - fields: [ - { - key: 'hosts', - value: null, - color: KpiHostsChartColors.hosts, - icon: 'storage', - }, - ], - enableAreaChart: true, - grow: 2, - description: i18n.HOSTS, - }, - { - key: 'authentication', - index: 1, - fields: [ - { - key: 'authSuccess', - name: i18n.SUCCESS_CHART_LABEL, - description: i18n.SUCCESS_UNIT_LABEL, - value: null, - color: KpiHostsChartColors.authSuccess, - icon: 'check', - }, - { - key: 'authFailure', - name: i18n.FAIL_CHART_LABEL, - description: i18n.FAIL_UNIT_LABEL, - value: null, - color: KpiHostsChartColors.authFailure, - icon: 'cross', - }, - ], - enableAreaChart: true, - enableBarChart: true, - grow: 4, - description: i18n.USER_AUTHENTICATIONS, - }, - { - key: 'uniqueIps', - index: 2, - fields: [ - { - key: 'uniqueSourceIps', - name: i18n.SOURCE_CHART_LABEL, - description: i18n.SOURCE_UNIT_LABEL, - value: null, - color: KpiHostsChartColors.uniqueSourceIps, - icon: 'visMapCoordinate', - }, - { - key: 'uniqueDestinationIps', - name: i18n.DESTINATION_CHART_LABEL, - description: i18n.DESTINATION_UNIT_LABEL, - value: null, - color: KpiHostsChartColors.uniqueDestinationIps, - icon: 'visMapCoordinate', - }, - ], - enableAreaChart: true, - enableBarChart: true, - grow: 4, - description: i18n.UNIQUE_IPS, - }, -]; diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/mock.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/mock.tsx deleted file mode 100644 index a1d081af20435..0000000000000 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/mock.tsx +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const mockKpiHostsData = { - hosts: 986, - hostsHistogram: [ - { - x: new Date('2019-05-03T13:00:00.000Z').valueOf(), - y: 919, - }, - { - x: new Date('2019-05-04T01:00:00.000Z').valueOf(), - y: 82, - }, - { - x: new Date('2019-05-04T13:00:00.000Z').valueOf(), - y: 4, - }, - ], - authSuccess: 61, - authSuccessHistogram: [ - { - x: new Date('2019-05-03T13:00:00.000Z').valueOf(), - y: 8, - }, - { - x: new Date('2019-05-04T01:00:00.000Z').valueOf(), - y: 52, - }, - { - x: new Date('2019-05-04T13:00:00.000Z').valueOf(), - y: 1, - }, - ], - authFailure: 15722, - authFailureHistogram: [ - { - x: new Date('2019-05-03T13:00:00.000Z').valueOf(), - y: 11731, - }, - { - x: new Date('2019-05-04T01:00:00.000Z').valueOf(), - y: 3979, - }, - { - x: new Date('2019-05-04T13:00:00.000Z').valueOf(), - y: 12, - }, - ], - uniqueSourceIps: 1407, - uniqueSourceIpsHistogram: [ - { - x: new Date('2019-05-03T13:00:00.000Z').valueOf(), - y: 1182, - }, - { - x: new Date('2019-05-04T01:00:00.000Z').valueOf(), - y: 364, - }, - { - x: new Date('2019-05-04T13:00:00.000Z').valueOf(), - y: 63, - }, - ], - uniqueDestinationIps: 1954, - uniqueDestinationIpsHistogram: [ - { - x: new Date('2019-05-03T13:00:00.000Z').valueOf(), - y: 1809, - }, - { - x: new Date('2019-05-04T01:00:00.000Z').valueOf(), - y: 407, - }, - { - x: new Date('2019-05-04T13:00:00.000Z').valueOf(), - y: 64, - }, - ], -}; -export const mockKpiHostDetailsData = { - authSuccess: 61, - authSuccessHistogram: [ - { - x: new Date('2019-05-03T13:00:00.000Z').valueOf(), - y: 8, - }, - { - x: new Date('2019-05-04T01:00:00.000Z').valueOf(), - y: 52, - }, - { - x: new Date('2019-05-04T13:00:00.000Z').valueOf(), - y: 1, - }, - ], - authFailure: 15722, - authFailureHistogram: [ - { - x: new Date('2019-05-03T13:00:00.000Z').valueOf(), - y: 11731, - }, - { - x: new Date('2019-05-04T01:00:00.000Z').valueOf(), - y: 3979, - }, - { - x: new Date('2019-05-04T13:00:00.000Z').valueOf(), - y: 12, - }, - ], - uniqueSourceIps: 1407, - uniqueSourceIpsHistogram: [ - { - x: new Date('2019-05-03T13:00:00.000Z').valueOf(), - y: 1182, - }, - { - x: new Date('2019-05-04T01:00:00.000Z').valueOf(), - y: 364, - }, - { - x: new Date('2019-05-04T13:00:00.000Z').valueOf(), - y: 63, - }, - ], - uniqueDestinationIps: 1954, - uniqueDestinationIpsHistogram: [ - { - x: new Date('2019-05-03T13:00:00.000Z').valueOf(), - y: 1809, - }, - { - x: new Date('2019-05-04T01:00:00.000Z').valueOf(), - y: 407, - }, - { - x: new Date('2019-05-04T13:00:00.000Z').valueOf(), - y: 64, - }, - ], -}; diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/types.ts b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/types.ts index fd48368124795..159d9dbcd005a 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/types.ts +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/types.ts @@ -4,9 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -export enum KpiHostsChartColors { - authSuccess = '#54B399', - authFailure = '#E7664C', +import { UpdateDateRange } from '../../../common/components/charts/common'; +import { GlobalTimeArgs } from '../../../common/containers/use_global_time'; + +export interface HostsKpiProps { + filterQuery: string; + from: string; + to: string; + indexNames: string[]; + narrowDateRange: UpdateDateRange; + setQuery: GlobalTimeArgs['setQuery']; + skip: boolean; +} + +export enum HostsKpiChartColors { + authenticationsSuccess = '#54B399', + authenticationsFailure = '#E7664C', uniqueSourceIps = '#D36086', uniqueDestinationIps = '#9170B8', hosts = '#6092C0', diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/index.tsx new file mode 100644 index 0000000000000..fc821ea8bbb55 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/index.tsx @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { StatItems } from '../../../../common/components/stat_items'; +import { useHostsKpiUniqueIps } from '../../../containers/kpi_hosts/unique_ips'; +import { HostsKpiBaseComponentManage } from '../common'; +import { HostsKpiProps, HostsKpiChartColors } from '../types'; +import * as i18n from './translations'; + +export const fieldsMapping: Readonly = [ + { + key: 'uniqueIps', + fields: [ + { + key: 'uniqueSourceIps', + name: i18n.SOURCE_CHART_LABEL, + description: i18n.SOURCE_UNIT_LABEL, + value: null, + color: HostsKpiChartColors.uniqueSourceIps, + icon: 'visMapCoordinate', + }, + { + key: 'uniqueDestinationIps', + name: i18n.DESTINATION_CHART_LABEL, + description: i18n.DESTINATION_UNIT_LABEL, + value: null, + color: HostsKpiChartColors.uniqueDestinationIps, + icon: 'visMapCoordinate', + }, + ], + enableAreaChart: true, + enableBarChart: true, + description: i18n.UNIQUE_IPS, + }, +]; + +const HostsKpiUniqueIpsComponent: React.FC = ({ + filterQuery, + from, + indexNames, + to, + narrowDateRange, + setQuery, + skip, +}) => { + const [loading, { refetch, id, inspect, ...data }] = useHostsKpiUniqueIps({ + filterQuery, + endDate: to, + indexNames, + startDate: from, + skip, + }); + + return ( + + ); +}; + +export const HostsKpiUniqueIps = React.memo(HostsKpiUniqueIpsComponent); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/translations.ts b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/translations.ts new file mode 100644 index 0000000000000..6cc651880be7b --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/translations.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const UNIQUE_IPS = i18n.translate('xpack.securitySolution.kpiHosts.uniqueIps.title', { + defaultMessage: 'Unique IPs', +}); + +export const SOURCE_UNIT_LABEL = i18n.translate( + 'xpack.securitySolution.kpiHosts.uniqueIps.sourceUnitLabel', + { + defaultMessage: 'source', + } +); + +export const DESTINATION_UNIT_LABEL = i18n.translate( + 'xpack.securitySolution.kpiHosts.uniqueIps.destinationUnitLabel', + { + defaultMessage: 'destination', + } +); + +export const SOURCE_CHART_LABEL = i18n.translate( + 'xpack.securitySolution.kpiHosts.uniqueIps.sourceChartLabel', + { + defaultMessage: 'Src.', + } +); + +export const DESTINATION_CHART_LABEL = i18n.translate( + 'xpack.securitySolution.kpiHosts.uniqueIps.destinationChartLabel', + { + defaultMessage: 'Dest.', + } +); diff --git a/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/index.test.tsx b/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/index.test.tsx index 5ace3439a2de6..41f443f14cafe 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/index.test.tsx @@ -30,18 +30,14 @@ describe('Uncommon Process Table Component', () => { const wrapper = shallow( @@ -54,18 +50,14 @@ describe('Uncommon Process Table Component', () => { const wrapper = mount( @@ -79,18 +71,14 @@ describe('Uncommon Process Table Component', () => { const wrapper = mount( @@ -105,18 +93,14 @@ describe('Uncommon Process Table Component', () => { const wrapper = mount( @@ -131,18 +115,14 @@ describe('Uncommon Process Table Component', () => { const wrapper = mount( @@ -157,18 +137,14 @@ describe('Uncommon Process Table Component', () => { const wrapper = mount( @@ -183,18 +159,14 @@ describe('Uncommon Process Table Component', () => { const wrapper = mount( @@ -208,18 +180,14 @@ describe('Uncommon Process Table Component', () => { const wrapper = mount( @@ -233,18 +201,14 @@ describe('Uncommon Process Table Component', () => { const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/index.tsx index 31d7fb10edb1c..c7025bb489ae4 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/index.tsx @@ -9,7 +9,10 @@ import React, { useCallback, useMemo } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { UncommonProcessesEdges, UncommonProcessItem } from '../../../graphql/types'; +import { + HostsUncommonProcessesEdges, + HostsUncommonProcessItem, +} from '../../../../common/search_strategy'; import { State } from '../../../common/store'; import { hostsActions, hostsModel, hostsSelectors } from '../../store'; import { defaultToEmptyTag, getEmptyValue } from '../../../common/components/empty_value'; @@ -21,7 +24,7 @@ import { getRowItemDraggables } from '../../../common/components/tables/helpers' import { HostsType } from '../../store/model'; const tableType = hostsModel.HostsTableType.uncommonProcesses; interface OwnProps { - data: UncommonProcessesEdges[]; + data: HostsUncommonProcessesEdges[]; fakeTotalCount: number; id: string; isInspect: boolean; @@ -33,12 +36,12 @@ interface OwnProps { } export type UncommonProcessTableColumns = [ - Columns, - Columns, - Columns, - Columns, - Columns, - Columns + Columns, + Columns, + Columns, + Columns, + Columns, + Columns ]; type UncommonProcessTableProps = OwnProps & PropsFromRedux; @@ -212,7 +215,7 @@ const getUncommonColumns = (): UncommonProcessTableColumns => [ }, ]; -export const getHostNames = (node: UncommonProcessItem): string[] => { +export const getHostNames = (node: HostsUncommonProcessItem): string[] => { if (node.hosts != null) { return node.hosts .filter((host) => host.name != null && host.name[0] != null) diff --git a/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/mock.ts b/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/mock.ts index 52b835278634b..56853c1bfaae1 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/mock.ts +++ b/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/mock.ts @@ -4,116 +4,115 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UncommonProcessesData } from '../../../graphql/types'; +import { HostsUncommonProcessesStrategyResponse } from '../../../../common/search_strategy'; -export const mockData: { UncommonProcess: UncommonProcessesData } = { - UncommonProcess: { - totalCount: 5, - edges: [ - { - node: { - _id: 'cPsuhGcB0WOhS6qyTKC0', - process: { - title: ['Hello World'], - name: ['elrond.elstc.co'], - }, - hosts: [], - instances: 93, - user: { - id: ['0'], - name: ['root'], - }, +export const mockData: HostsUncommonProcessesStrategyResponse = { + totalCount: 5, + edges: [ + { + node: { + _id: 'cPsuhGcB0WOhS6qyTKC0', + process: { + title: ['Hello World'], + name: ['elrond.elstc.co'], }, - cursor: { - value: '98966fa2013c396155c460d35c0902be', + hosts: [], + instances: 93, + user: { + id: ['0'], + name: ['root'], }, }, - { - node: { - _id: 'cPsuhGcB0WOhS6qyTKC0', - process: { - title: ['Hello World'], - name: ['elrond.elstc.co'], - }, - hosts: [{ id: ['host-id-1'], name: ['hello-world'] }], - instances: 93, - user: { - id: ['0'], - name: ['root'], - }, + cursor: { + value: '98966fa2013c396155c460d35c0902be', + }, + }, + { + node: { + _id: 'cPsuhGcB0WOhS6qyTKC0', + process: { + title: ['Hello World'], + name: ['elrond.elstc.co'], }, - cursor: { - value: '98966fa2013c396155c460d35c0902be', + hosts: [{ id: ['host-id-1'], name: ['hello-world'] }], + instances: 93, + user: { + id: ['0'], + name: ['root'], }, }, - { - node: { - _id: 'KwQDiWcB0WOhS6qyXmrW', - process: { - title: ['Hello World'], - name: ['siem-kibana'], - }, - hosts: [ - { id: ['host-id-1'], name: ['hello-world'] }, - { id: ['host-id-2'], name: ['hello-world-2'] }, - ], - instances: 97, - user: { - id: ['1'], - name: ['Evan'], - }, + cursor: { + value: '98966fa2013c396155c460d35c0902be', + }, + }, + { + node: { + _id: 'KwQDiWcB0WOhS6qyXmrW', + process: { + title: ['Hello World'], + name: ['siem-kibana'], }, - cursor: { - value: 'aa7ca589f1b8220002f2fc61c64cfbf1', + hosts: [ + { id: ['host-id-1'], name: ['hello-world'] }, + { id: ['host-id-2'], name: ['hello-world-2'] }, + ], + instances: 97, + user: { + id: ['1'], + name: ['Evan'], }, }, - { - node: { - _id: 'KwQDiWcB0WOhS6qyXmrW', - process: { - title: ['Hello World'], - name: ['siem-kibana'], - }, - hosts: [{ ip: ['127.0.0.1'] }], - instances: 97, - user: { - id: ['1'], - name: ['Evan'], - }, + cursor: { + value: 'aa7ca589f1b8220002f2fc61c64cfbf1', + }, + }, + { + node: { + _id: 'KwQDiWcB0WOhS6qyXmrW', + process: { + title: ['Hello World'], + name: ['siem-kibana'], }, - cursor: { - value: 'aa7ca589f1b8220002f2fc61c64cfbf1', + hosts: [{ ip: ['127.0.0.1'] }], + instances: 97, + user: { + id: ['1'], + name: ['Evan'], }, }, - { - node: { - _id: 'KwQDiWcB0WOhS6qyXmrW', - process: { - title: ['Hello World'], - name: ['siem-kibana'], - }, - hosts: [ - { ip: ['127.0.0.1'] }, - { id: ['host-id-1'], name: ['hello-world'] }, - { ip: ['127.0.0.1'] }, - { id: ['host-id-2'], name: ['hello-world-2'] }, - { ip: ['127.0.0.1'] }, - ], - instances: 97, - user: { - id: ['1'], - name: ['Evan'], - }, + cursor: { + value: 'aa7ca589f1b8220002f2fc61c64cfbf1', + }, + }, + { + node: { + _id: 'KwQDiWcB0WOhS6qyXmrW', + process: { + title: ['Hello World'], + name: ['siem-kibana'], }, - cursor: { - value: 'aa7ca589f1b8220002f2fc61c64cfbf1', + hosts: [ + { ip: ['127.0.0.1'] }, + { id: ['host-id-1'], name: ['hello-world'] }, + { ip: ['127.0.0.1'] }, + { id: ['host-id-2'], name: ['hello-world-2'] }, + { ip: ['127.0.0.1'] }, + ], + instances: 97, + user: { + id: ['1'], + name: ['Evan'], }, }, - ], - pageInfo: { - activePage: 1, - fakeTotalCount: 50, - showMorePagesIndicator: true, + cursor: { + value: 'aa7ca589f1b8220002f2fc61c64cfbf1', + }, }, + ], + pageInfo: { + activePage: 1, + fakeTotalCount: 50, + showMorePagesIndicator: true, }, + rawResponse: {} as HostsUncommonProcessesStrategyResponse['rawResponse'], }; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx index 34f2385051f4c..b1563e85c93dd 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx @@ -9,9 +9,12 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { shallowEqual, useSelector } from 'react-redux'; import deepEqual from 'fast-deep-equal'; -import { AbortError } from '../../../../../../../src/plugins/data/common'; +import { + AbortError, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../src/plugins/data/common'; -import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import { HostsQueries } from '../../../../common/search_strategy/security_solution'; import { HostAuthenticationsRequestOptions, @@ -52,6 +55,7 @@ interface UseAuthentications { docValueFields?: DocValueFields[]; filterQuery?: ESTermQuery | string; endDate: string; + indexNames: string[]; startDate: string; type: hostsModel.HostsType; skip: boolean; @@ -61,6 +65,7 @@ export const useAuthentications = ({ docValueFields, filterQuery, endDate, + indexNames, startDate, type, skip, @@ -70,15 +75,14 @@ export const useAuthentications = ({ (state: State) => getAuthenticationsSelector(state, type), shallowEqual ); - const { data, notifications, uiSettings } = useKibana().services; + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); const [loading, setLoading] = useState(false); const [authenticationsRequest, setAuthenticationsRequest] = useState< HostAuthenticationsRequestOptions >({ - defaultIndex, + defaultIndex: indexNames, docValueFields: docValueFields ?? [], factoryQueryType: HostsQueries.authentications, filterQuery: createFilter(filterQuery), @@ -136,7 +140,7 @@ export const useAuthentications = ({ }) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setAuthenticationsResponse((prevResponse) => ({ @@ -149,7 +153,7 @@ export const useAuthentications = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -182,7 +186,7 @@ export const useAuthentications = ({ setAuthenticationsRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, docValueFields: docValueFields ?? [], filterQuery: createFilter(filterQuery), pagination: generateTablePaginationOptions(activePage, limit), @@ -197,7 +201,7 @@ export const useAuthentications = ({ } return prevRequest; }); - }, [activePage, defaultIndex, docValueFields, endDate, filterQuery, limit, skip, startDate]); + }, [activePage, docValueFields, endDate, filterQuery, indexNames, limit, skip, startDate]); useEffect(() => { authenticationsSearch(authenticationsRequest); diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/_index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/_index.tsx index 7b248d867bb76..5b69e20398a35 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/_index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/_index.tsx @@ -10,7 +10,6 @@ import deepEqual from 'fast-deep-equal'; import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; -import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; import { inputsModel } from '../../../../common/store'; import { useKibana } from '../../../../common/lib/kibana'; import { @@ -21,7 +20,11 @@ import { } from '../../../../../common/search_strategy/security_solution/hosts'; import * as i18n from './translations'; -import { AbortError } from '../../../../../../../../src/plugins/data/common'; +import { + AbortError, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../../src/plugins/data/common'; import { getInspectResponse } from '../../../../helpers'; import { InspectResponse } from '../../../../types'; @@ -37,9 +40,10 @@ export interface HostDetailsArgs { } interface UseHostDetails { - id?: string; - hostName: string; endDate: string; + hostName: string; + id?: string; + indexNames: string[]; skip?: boolean; startDate: string; } @@ -47,17 +51,17 @@ interface UseHostDetails { export const useHostDetails = ({ endDate, hostName, + indexNames, + id = ID, skip = false, startDate, - id = ID, }: UseHostDetails): [boolean, HostDetailsArgs] => { - const { data, notifications, uiSettings } = useKibana().services; + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); const [loading, setLoading] = useState(false); const [hostDetailsRequest, setHostDetailsRequest] = useState({ - defaultIndex, + defaultIndex: indexNames, hostName, factoryQueryType: HostsQueries.details, timerange: { @@ -93,7 +97,7 @@ export const useHostDetails = ({ }) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setHostDetailsResponse((prevResponse) => ({ @@ -104,7 +108,7 @@ export const useHostDetails = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -138,7 +142,7 @@ export const useHostDetails = ({ setHostDetailsRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, hostName, timerange: { interval: '12h', @@ -151,7 +155,7 @@ export const useHostDetails = ({ } return prevRequest; }); - }, [defaultIndex, endDate, hostName, startDate, skip]); + }, [endDate, hostName, indexNames, startDate, skip]); useEffect(() => { hostDetailsSearch(hostDetailsRequest); diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/index.tsx index 12a82c7980b61..0236270d18618 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/index.tsx @@ -10,11 +10,9 @@ import { Query } from 'react-apollo'; import { connect } from 'react-redux'; import { compose } from 'redux'; -import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; import { inputsModel, inputsSelectors, State } from '../../../../common/store'; import { getDefaultFetchPolicy } from '../../../../common/containers/helpers'; import { QueryTemplate, QueryTemplateProps } from '../../../../common/containers/query_template'; -import { withKibana, WithKibanaProps } from '../../../../common/lib/kibana'; import { HostOverviewQuery } from './host_overview.gql_query'; import { GetHostOverviewQuery, HostItem } from '../../../../graphql/types'; @@ -42,7 +40,7 @@ export interface OwnProps extends QueryTemplateProps { endDate: string; } -type HostsOverViewProps = OwnProps & HostOverviewReduxProps & WithKibanaProps; +type HostsOverViewProps = OwnProps & HostOverviewReduxProps; class HostOverviewByNameComponentQuery extends QueryTemplate< HostsOverViewProps, @@ -52,10 +50,10 @@ class HostOverviewByNameComponentQuery extends QueryTemplate< public render() { const { id = ID, + indexNames, isInspected, children, hostName, - kibana, skip, sourceId, startDate, @@ -75,7 +73,7 @@ class HostOverviewByNameComponentQuery extends QueryTemplate< from: startDate, to: endDate, }, - defaultIndex: kibana.services.uiSettings.get(DEFAULT_INDEX_KEY), + defaultIndex: indexNames, inspect: isInspected, }} > @@ -108,6 +106,5 @@ const makeMapStateToProps = () => { }; export const HostOverviewByNameQuery = compose>( - connect(makeMapStateToProps), - withKibana + connect(makeMapStateToProps) )(HostOverviewByNameComponentQuery); diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx index 169fe58e9a2cc..cc944a59571f1 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx @@ -7,18 +7,20 @@ import deepEqual from 'fast-deep-equal'; import { useCallback, useEffect, useRef, useState } from 'react'; -import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; - import { useKibana } from '../../../../common/lib/kibana'; import { HostsQueries, HostFirstLastSeenRequestOptions, HostFirstLastSeenStrategyResponse, } from '../../../../../common/search_strategy/security_solution'; -import { useWithSource } from '../../../../common/containers/source'; import * as i18n from './translations'; -import { AbortError } from '../../../../../../../../src/plugins/data/common'; +import { DocValueFields } from '../../../../../common/search_strategy'; +import { + AbortError, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../../src/plugins/data/common'; const ID = 'firstLastSeenHostQuery'; @@ -29,21 +31,23 @@ export interface FirstLastSeenHostArgs { lastSeen?: string | null; } interface UseHostFirstLastSeen { + docValueFields: DocValueFields[]; hostName: string; + indexNames: string[]; } export const useFirstLastSeenHost = ({ + docValueFields, hostName, + indexNames, }: UseHostFirstLastSeen): [boolean, FirstLastSeenHostArgs] => { - const { docValueFields } = useWithSource('default'); - const { data, notifications, uiSettings } = useKibana().services; + const { data, notifications } = useKibana().services; const abortCtrl = useRef(new AbortController()); - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); const [loading, setLoading] = useState(false); const [firstLastSeenHostRequest, setFirstLastSeenHostRequest] = useState< HostFirstLastSeenRequestOptions >({ - defaultIndex, + defaultIndex: indexNames, docValueFields: docValueFields ?? [], factoryQueryType: HostsQueries.firstLastSeen, hostName, @@ -72,7 +76,7 @@ export const useFirstLastSeenHost = ({ }) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setFirstLastSeenHostResponse((prevResponse) => ({ @@ -83,7 +87,7 @@ export const useFirstLastSeenHost = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -120,7 +124,7 @@ export const useFirstLastSeenHost = ({ setFirstLastSeenHostRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, docValueFields: docValueFields ?? [], hostName, }; @@ -129,7 +133,7 @@ export const useFirstLastSeenHost = ({ } return prevRequest; }); - }, [defaultIndex, docValueFields, hostName]); + }, [indexNames, docValueFields, hostName]); useEffect(() => { firstLastSeenHostSearch(firstLastSeenHostRequest); diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx index 11b853e0ebeb0..6ca0272e58d7d 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx @@ -9,7 +9,6 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; -import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import { inputsModel, State } from '../../../common/store'; import { createFilter } from '../../../common/containers/helpers'; import { useKibana } from '../../../common/lib/kibana'; @@ -26,7 +25,11 @@ import { import { ESTermQuery } from '../../../../common/typed_json'; import * as i18n from './translations'; -import { AbortError } from '../../../../../../../src/plugins/data/common'; +import { + AbortError, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../src/plugins/data/common'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; @@ -50,6 +53,7 @@ interface UseAllHost { docValueFields?: DocValueFields[]; filterQuery?: ESTermQuery | string; endDate: string; + indexNames: string[]; skip?: boolean; startDate: string; type: hostsModel.HostsType; @@ -59,6 +63,7 @@ export const useAllHost = ({ docValueFields, filterQuery, endDate, + indexNames, skip = false, startDate, type, @@ -67,13 +72,12 @@ export const useAllHost = ({ const { activePage, direction, limit, sortField } = useSelector((state: State) => getHostsSelector(state, type) ); - const { data, notifications, uiSettings } = useKibana().services; + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); const [loading, setLoading] = useState(false); const [hostsRequest, setHostRequest] = useState({ - defaultIndex, + defaultIndex: indexNames, docValueFields: docValueFields ?? [], factoryQueryType: HostsQueries.hosts, filterQuery: createFilter(filterQuery), @@ -133,7 +137,7 @@ export const useAllHost = ({ }) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setHostsResponse((prevResponse) => ({ @@ -146,7 +150,7 @@ export const useAllHost = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -177,7 +181,7 @@ export const useAllHost = ({ setHostRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, docValueFields: docValueFields ?? [], filterQuery: createFilter(filterQuery), pagination: generateTablePaginationOptions(activePage, limit), @@ -198,11 +202,11 @@ export const useAllHost = ({ }); }, [ activePage, - defaultIndex, direction, docValueFields, endDate, filterQuery, + indexNames, limit, skip, startDate, diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_host_details/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_host_details/index.tsx index 1551e7d706714..26e4eaf9ea82e 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_host_details/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_host_details/index.tsx @@ -9,10 +9,8 @@ import React from 'react'; import { Query } from 'react-apollo'; import { connect, ConnectedProps } from 'react-redux'; -import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import { KpiHostDetailsData, GetKpiHostDetailsQuery } from '../../../graphql/types'; import { inputsModel, inputsSelectors, State } from '../../../common/store'; -import { useUiSetting } from '../../../common/lib/kibana'; import { createFilter, getDefaultFetchPolicy } from '../../../common/containers/helpers'; import { QueryTemplateProps } from '../../../common/containers/query_template'; @@ -33,7 +31,17 @@ export interface QueryKpiHostDetailsProps extends QueryTemplateProps { } const KpiHostDetailsComponentQuery = React.memo( - ({ id = ID, children, endDate, filterQuery, isInspected, skip, sourceId, startDate }) => ( + ({ + id = ID, + children, + endDate, + filterQuery, + indexNames, + isInspected, + skip, + sourceId, + startDate, + }) => ( query={kpiHostDetailsQuery} fetchPolicy={getDefaultFetchPolicy()} @@ -47,7 +55,7 @@ const KpiHostDetailsComponentQuery = React.memo(DEFAULT_INDEX_KEY), + defaultIndex: indexNames ?? [], inspect: isInspected, }} > diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx new file mode 100644 index 0000000000000..404231be1e6cd --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx @@ -0,0 +1,170 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import deepEqual from 'fast-deep-equal'; +import { noop } from 'lodash/fp'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { inputsModel } from '../../../../common/store'; +import { createFilter } from '../../../../common/containers/helpers'; +import { useKibana } from '../../../../common/lib/kibana'; +import { + HostsKpiQueries, + HostsKpiAuthenticationsRequestOptions, + HostsKpiAuthenticationsStrategyResponse, +} from '../../../../../common/search_strategy'; +import { ESTermQuery } from '../../../../../common/typed_json'; + +import * as i18n from './translations'; +import { AbortError } from '../../../../../../../../src/plugins/data/common'; +import { getInspectResponse } from '../../../../helpers'; +import { InspectResponse } from '../../../../types'; + +const ID = 'hostsKpiAuthenticationsQuery'; + +export interface HostsKpiAuthenticationsArgs + extends Omit { + id: string; + inspect: InspectResponse; + isInspected: boolean; + refetch: inputsModel.Refetch; +} + +interface UseHostsKpiAuthentications { + filterQuery?: ESTermQuery | string; + endDate: string; + indexNames: string[]; + skip?: boolean; + startDate: string; +} + +export const useHostsKpiAuthentications = ({ + filterQuery, + endDate, + indexNames, + skip = false, + startDate, +}: UseHostsKpiAuthentications): [boolean, HostsKpiAuthenticationsArgs] => { + const { data, notifications } = useKibana().services; + const refetch = useRef(noop); + const abortCtrl = useRef(new AbortController()); + const [loading, setLoading] = useState(false); + const [hostsKpiAuthenticationsRequest, setHostsKpiAuthenticationsRequest] = useState< + HostsKpiAuthenticationsRequestOptions + >({ + defaultIndex: indexNames, + factoryQueryType: HostsKpiQueries.kpiAuthentications, + filterQuery: createFilter(filterQuery), + id: ID, + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }); + + const [hostsKpiAuthenticationsResponse, setHostsKpiAuthenticationsResponse] = useState< + HostsKpiAuthenticationsArgs + >({ + authenticationsSuccess: 0, + authenticationsSuccessHistogram: [], + authenticationsFailure: 0, + authenticationsFailureHistogram: [], + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + refetch: refetch.current, + }); + + const hostsKpiAuthenticationsSearch = useCallback( + (request: HostsKpiAuthenticationsRequestOptions) => { + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + + const searchSubscription$ = data.search + .search( + request, + { + strategy: 'securitySolutionSearchStrategy', + abortSignal: abortCtrl.current.signal, + } + ) + .subscribe({ + next: (response) => { + if (!response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + setHostsKpiAuthenticationsResponse((prevResponse) => ({ + ...prevResponse, + authenticationsSuccess: response.authenticationsSuccess, + authenticationsSuccessHistogram: response.authenticationsSuccessHistogram, + authenticationsFailure: response.authenticationsFailure, + authenticationsFailureHistogram: response.authenticationsFailureHistogram, + inspect: getInspectResponse(response, prevResponse.inspect), + refetch: refetch.current, + })); + } + searchSubscription$.unsubscribe(); + } else if (response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + } + // TODO: Make response error status clearer + notifications.toasts.addWarning(i18n.ERROR_HOSTS_KPI_AUTHENTICATIONS); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ + title: i18n.FAIL_HOSTS_KPI_AUTHENTICATIONS, + text: msg.message, + }); + } + }, + }); + }; + abortCtrl.current.abort(); + asyncSearch(); + refetch.current = asyncSearch; + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts] + ); + + useEffect(() => { + setHostsKpiAuthenticationsRequest((prevRequest) => { + const myRequest = { + ...prevRequest, + defaultIndex: indexNames, + filterQuery: createFilter(filterQuery), + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }; + if (!skip && !deepEqual(prevRequest, myRequest)) { + return myRequest; + } + return prevRequest; + }); + }, [indexNames, endDate, filterQuery, skip, startDate]); + + useEffect(() => { + hostsKpiAuthenticationsSearch(hostsKpiAuthenticationsRequest); + }, [hostsKpiAuthenticationsRequest, hostsKpiAuthenticationsSearch]); + + return [loading, hostsKpiAuthenticationsResponse]; +}; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/translations.ts b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/translations.ts new file mode 100644 index 0000000000000..fb5af83d0acef --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/translations.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ERROR_HOSTS_KPI_AUTHENTICATIONS = i18n.translate( + 'xpack.securitySolution.hostsKpiAuthentications.errorSearchDescription', + { + defaultMessage: `An error has occurred on hosts kpi authentications search`, + } +); + +export const FAIL_HOSTS_KPI_AUTHENTICATIONS = i18n.translate( + 'xpack.securitySolution.hostsKpiAuthentications.failSearchDescription', + { + defaultMessage: `Failed to run search on hosts kpi authentications`, + } +); diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx new file mode 100644 index 0000000000000..bb918a9214f40 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx @@ -0,0 +1,158 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import deepEqual from 'fast-deep-equal'; +import { noop } from 'lodash/fp'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { inputsModel } from '../../../../common/store'; +import { createFilter } from '../../../../common/containers/helpers'; +import { useKibana } from '../../../../common/lib/kibana'; +import { + HostsKpiQueries, + HostsKpiHostsRequestOptions, + HostsKpiHostsStrategyResponse, +} from '../../../../../common/search_strategy'; +import { ESTermQuery } from '../../../../../common/typed_json'; + +import * as i18n from './translations'; +import { AbortError } from '../../../../../../../../src/plugins/data/common'; +import { getInspectResponse } from '../../../../helpers'; +import { InspectResponse } from '../../../../types'; + +const ID = 'hostsKpiHostsQuery'; + +export interface HostsKpiHostsArgs extends Omit { + id: string; + inspect: InspectResponse; + isInspected: boolean; + refetch: inputsModel.Refetch; +} + +interface UseHostsKpiHosts { + filterQuery?: ESTermQuery | string; + endDate: string; + indexNames: string[]; + skip?: boolean; + startDate: string; +} + +export const useHostsKpiHosts = ({ + filterQuery, + endDate, + indexNames, + skip = false, + startDate, +}: UseHostsKpiHosts): [boolean, HostsKpiHostsArgs] => { + const { data, notifications } = useKibana().services; + const refetch = useRef(noop); + const abortCtrl = useRef(new AbortController()); + const [loading, setLoading] = useState(false); + const [hostsKpiHostsRequest, setHostsKpiHostsRequest] = useState({ + defaultIndex: indexNames, + factoryQueryType: HostsKpiQueries.kpiHosts, + filterQuery: createFilter(filterQuery), + id: ID, + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }); + + const [hostsKpiHostsResponse, setHostsKpiHostsResponse] = useState({ + hosts: 0, + hostsHistogram: [], + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + refetch: refetch.current, + }); + + const hostsKpiHostsSearch = useCallback( + (request: HostsKpiHostsRequestOptions) => { + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + + const searchSubscription$ = data.search + .search(request, { + strategy: 'securitySolutionSearchStrategy', + abortSignal: abortCtrl.current.signal, + }) + .subscribe({ + next: (response) => { + if (!response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + setHostsKpiHostsResponse((prevResponse) => ({ + ...prevResponse, + hosts: response.hosts, + hostsHistogram: response.hostsHistogram, + inspect: getInspectResponse(response, prevResponse.inspect), + refetch: refetch.current, + })); + } + searchSubscription$.unsubscribe(); + } else if (response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + } + // TODO: Make response error status clearer + notifications.toasts.addWarning(i18n.ERROR_HOSTS_KPI_HOSTS); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ + title: i18n.FAIL_HOSTS_KPI_HOSTS, + text: msg.message, + }); + } + }, + }); + }; + abortCtrl.current.abort(); + asyncSearch(); + refetch.current = asyncSearch; + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts] + ); + + useEffect(() => { + setHostsKpiHostsRequest((prevRequest) => { + const myRequest = { + ...prevRequest, + defaultIndex: indexNames, + filterQuery: createFilter(filterQuery), + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }; + if (!skip && !deepEqual(prevRequest, myRequest)) { + return myRequest; + } + return prevRequest; + }); + }, [indexNames, endDate, filterQuery, skip, startDate]); + + useEffect(() => { + hostsKpiHostsSearch(hostsKpiHostsRequest); + }, [hostsKpiHostsRequest, hostsKpiHostsSearch]); + + return [loading, hostsKpiHostsResponse]; +}; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/translations.ts b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/translations.ts new file mode 100644 index 0000000000000..2a15563a4b1cd --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/translations.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ERROR_HOSTS_KPI_HOSTS = i18n.translate( + 'xpack.securitySolution.hostsKpiHosts.errorSearchDescription', + { + defaultMessage: `An error has occurred on hosts kpi hosts search`, + } +); + +export const FAIL_HOSTS_KPI_HOSTS = i18n.translate( + 'xpack.securitySolution.hostsKpiHosts.failSearchDescription', + { + defaultMessage: `Failed to run search on hosts kpi hosts`, + } +); diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/index.tsx index 1a6df58f04597..c0ae767219aae 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/index.tsx @@ -4,82 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getOr } from 'lodash/fp'; -import React from 'react'; -import { Query } from 'react-apollo'; -import { connect, ConnectedProps } from 'react-redux'; - -import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; -import { GetKpiHostsQuery, KpiHostsData } from '../../../graphql/types'; -import { inputsModel, inputsSelectors, State } from '../../../common/store'; -import { useUiSetting } from '../../../common/lib/kibana'; -import { createFilter, getDefaultFetchPolicy } from '../../../common/containers/helpers'; -import { QueryTemplateProps } from '../../../common/containers/query_template'; - -import { kpiHostsQuery } from './index.gql_query'; - -const ID = 'kpiHostsQuery'; - -export interface KpiHostsArgs { - id: string; - inspect: inputsModel.InspectQuery; - kpiHosts: KpiHostsData; - loading: boolean; - refetch: inputsModel.Refetch; -} - -export interface KpiHostsProps extends QueryTemplateProps { - children: (args: KpiHostsArgs) => React.ReactNode; -} - -const KpiHostsComponentQuery = React.memo( - ({ id = ID, children, endDate, filterQuery, isInspected, skip, sourceId, startDate }) => ( - - query={kpiHostsQuery} - fetchPolicy={getDefaultFetchPolicy()} - notifyOnNetworkStatusChange - skip={skip} - variables={{ - sourceId, - timerange: { - interval: '12h', - from: startDate!, - to: endDate!, - }, - filterQuery: createFilter(filterQuery), - defaultIndex: useUiSetting(DEFAULT_INDEX_KEY), - inspect: isInspected, - }} - > - {({ data, loading, refetch }) => { - const kpiHosts = getOr({}, `source.KpiHosts`, data); - return children({ - id, - inspect: getOr(null, 'source.KpiHosts.inspect', data), - kpiHosts, - loading, - refetch, - }); - }} - - ) -); - -KpiHostsComponentQuery.displayName = 'KpiHostsComponentQuery'; - -const makeMapStateToProps = () => { - const getQuery = inputsSelectors.globalQueryByIdSelector(); - const mapStateToProps = (state: State, { id = ID }: KpiHostsProps) => { - const { isInspected } = getQuery(state, id); - return { - isInspected, - }; - }; - return mapStateToProps; -}; - -const connector = connect(makeMapStateToProps); - -type PropsFromRedux = ConnectedProps; - -export const KpiHostsQuery = connector(KpiHostsComponentQuery); +export * from './authentications'; +export * from './hosts'; +export * from './unique_ips'; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx new file mode 100644 index 0000000000000..b8e93eef8dc91 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx @@ -0,0 +1,167 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import deepEqual from 'fast-deep-equal'; +import { noop } from 'lodash/fp'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { inputsModel } from '../../../../common/store'; +import { createFilter } from '../../../../common/containers/helpers'; +import { useKibana } from '../../../../common/lib/kibana'; +import { + HostsKpiQueries, + HostsKpiUniqueIpsRequestOptions, + HostsKpiUniqueIpsStrategyResponse, +} from '../../../../../common/search_strategy'; +import { ESTermQuery } from '../../../../../common/typed_json'; + +import * as i18n from './translations'; +import { AbortError } from '../../../../../../../../src/plugins/data/common'; +import { getInspectResponse } from '../../../../helpers'; +import { InspectResponse } from '../../../../types'; + +const ID = 'hostsKpiUniqueIpsQuery'; + +export interface HostsKpiUniqueIpsArgs + extends Omit { + id: string; + inspect: InspectResponse; + isInspected: boolean; + refetch: inputsModel.Refetch; +} + +interface UseHostsKpiUniqueIps { + filterQuery?: ESTermQuery | string; + endDate: string; + indexNames: string[]; + skip?: boolean; + startDate: string; +} + +export const useHostsKpiUniqueIps = ({ + filterQuery, + endDate, + indexNames, + skip = false, + startDate, +}: UseHostsKpiUniqueIps): [boolean, HostsKpiUniqueIpsArgs] => { + const { data, notifications } = useKibana().services; + const refetch = useRef(noop); + const abortCtrl = useRef(new AbortController()); + const [loading, setLoading] = useState(false); + const [hostsKpiUniqueIpsRequest, setHostsKpiUniqueIpsRequest] = useState< + HostsKpiUniqueIpsRequestOptions + >({ + defaultIndex: indexNames, + factoryQueryType: HostsKpiQueries.kpiUniqueIps, + filterQuery: createFilter(filterQuery), + id: ID, + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }); + + const [hostsKpiUniqueIpsResponse, setHostsKpiUniqueIpsResponse] = useState( + { + uniqueSourceIps: 0, + uniqueSourceIpsHistogram: [], + uniqueDestinationIps: 0, + uniqueDestinationIpsHistogram: [], + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + refetch: refetch.current, + } + ); + + const hostsKpiUniqueIpsSearch = useCallback( + (request: HostsKpiUniqueIpsRequestOptions) => { + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + + const searchSubscription$ = data.search + .search(request, { + strategy: 'securitySolutionSearchStrategy', + abortSignal: abortCtrl.current.signal, + }) + .subscribe({ + next: (response) => { + if (!response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + setHostsKpiUniqueIpsResponse((prevResponse) => ({ + ...prevResponse, + uniqueSourceIps: response.uniqueSourceIps, + uniqueSourceIpsHistogram: response.uniqueSourceIpsHistogram, + uniqueDestinationIps: response.uniqueDestinationIps, + uniqueDestinationIpsHistogram: response.uniqueDestinationIpsHistogram, + inspect: getInspectResponse(response, prevResponse.inspect), + refetch: refetch.current, + })); + } + searchSubscription$.unsubscribe(); + } else if (response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + } + // TODO: Make response error status clearer + notifications.toasts.addWarning(i18n.ERROR_HOSTS_KPI_UNIQUE_IPS); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ + title: i18n.FAIL_HOSTS_KPI_UNIQUE_IPS, + text: msg.message, + }); + } + }, + }); + }; + abortCtrl.current.abort(); + asyncSearch(); + refetch.current = asyncSearch; + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts] + ); + + useEffect(() => { + setHostsKpiUniqueIpsRequest((prevRequest) => { + const myRequest = { + ...prevRequest, + defaultIndex: indexNames, + filterQuery: createFilter(filterQuery), + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }; + if (!skip && !deepEqual(prevRequest, myRequest)) { + return myRequest; + } + return prevRequest; + }); + }, [indexNames, endDate, filterQuery, skip, startDate]); + + useEffect(() => { + hostsKpiUniqueIpsSearch(hostsKpiUniqueIpsRequest); + }, [hostsKpiUniqueIpsRequest, hostsKpiUniqueIpsSearch]); + + return [loading, hostsKpiUniqueIpsResponse]; +}; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/translations.ts b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/translations.ts new file mode 100644 index 0000000000000..2d1574b080ac1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/translations.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ERROR_HOSTS_KPI_UNIQUE_IPS = i18n.translate( + 'xpack.securitySolution.hostsKpiUniqueIps.errorSearchDescription', + { + defaultMessage: `An error has occurred on hosts kpi unique ips search`, + } +); + +export const FAIL_HOSTS_KPI_UNIQUE_IPS = i18n.translate( + 'xpack.securitySolution.hostsKpiUniqueIps.failSearchDescription', + { + defaultMessage: `Failed to run search on hosts kpi unique ips`, + } +); diff --git a/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.gql_query.ts b/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.gql_query.ts deleted file mode 100644 index d984de020faa1..0000000000000 --- a/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.gql_query.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import gql from 'graphql-tag'; - -export const uncommonProcessesQuery = gql` - query GetUncommonProcessesQuery( - $sourceId: ID! - $timerange: TimerangeInput! - $pagination: PaginationInputPaginated! - $filterQuery: String - $defaultIndex: [String!]! - $inspect: Boolean! - ) { - source(id: $sourceId) { - id - UncommonProcesses( - timerange: $timerange - pagination: $pagination - filterQuery: $filterQuery - defaultIndex: $defaultIndex - ) { - totalCount - edges { - node { - _id - instances - process { - args - name - } - user { - id - name - } - hosts { - name - } - } - cursor { - value - } - } - pageInfo { - activePage - fakeTotalCount - showMorePagesIndicator - } - inspect @include(if: $inspect) { - dsl - response - } - } - } - } -`; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx index 82f5a97e9e413..4036837024025 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx @@ -9,22 +9,26 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; -import { AbortError } from '../../../../../../../src/plugins/data/common'; +import { + AbortError, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../src/plugins/data/common'; -import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; -import { PageInfoPaginated, UncommonProcessesEdges } from '../../../graphql/types'; import { inputsModel, State } from '../../../common/store'; import { useKibana } from '../../../common/lib/kibana'; import { generateTablePaginationOptions } from '../../../common/components/paginated_table/helpers'; import { createFilter } from '../../../common/containers/helpers'; - import { hostsModel, hostsSelectors } from '../../store'; import { - HostUncommonProcessesRequestOptions, - HostUncommonProcessesStrategyResponse, -} from '../../../../common/search_strategy/security_solution/hosts/uncommon_processes'; -import { HostsQueries } from '../../../../common/search_strategy/security_solution/hosts'; -import { DocValueFields, SortField } from '../../../../common/search_strategy'; + DocValueFields, + SortField, + PageInfoPaginated, + HostsUncommonProcessesEdges, + HostsQueries, + HostsUncommonProcessesRequestOptions, + HostsUncommonProcessesStrategyResponse, +} from '../../../../common/search_strategy'; import * as i18n from './translations'; import { ESTermQuery } from '../../../../common/typed_json'; @@ -41,13 +45,14 @@ export interface UncommonProcessesArgs { pageInfo: PageInfoPaginated; refetch: inputsModel.Refetch; totalCount: number; - uncommonProcesses: UncommonProcessesEdges[]; + uncommonProcesses: HostsUncommonProcessesEdges[]; } interface UseUncommonProcesses { docValueFields?: DocValueFields[]; filterQuery?: ESTermQuery | string; endDate: string; + indexNames: string[]; skip?: boolean; startDate: string; type: hostsModel.HostsType; @@ -57,6 +62,7 @@ export const useUncommonProcesses = ({ docValueFields, filterQuery, endDate, + indexNames, skip = false, startDate, type, @@ -65,15 +71,14 @@ export const useUncommonProcesses = ({ const { activePage, limit } = useSelector((state: State) => getUncommonProcessesSelector(state, type) ); - const { data, notifications, uiSettings } = useKibana().services; + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); const [loading, setLoading] = useState(false); const [uncommonProcessesRequest, setUncommonProcessesRequest] = useState< - HostUncommonProcessesRequestOptions + HostsUncommonProcessesRequestOptions >({ - defaultIndex, + defaultIndex: indexNames, docValueFields: docValueFields ?? [], factoryQueryType: HostsQueries.uncommonProcesses, filterQuery: createFilter(filterQuery), @@ -119,14 +124,14 @@ export const useUncommonProcesses = ({ ); const uncommonProcessesSearch = useCallback( - (request: HostUncommonProcessesRequestOptions) => { + (request: HostsUncommonProcessesRequestOptions) => { let didCancel = false; const asyncSearch = async () => { abortCtrl.current = new AbortController(); setLoading(true); const searchSubscription$ = data.search - .search( + .search( request, { strategy: 'securitySolutionSearchStrategy', @@ -135,7 +140,7 @@ export const useUncommonProcesses = ({ ) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setUncommonProcessesResponse((prevResponse) => ({ @@ -148,7 +153,7 @@ export const useUncommonProcesses = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -181,7 +186,7 @@ export const useUncommonProcesses = ({ setUncommonProcessesRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, docValueFields: docValueFields ?? [], filterQuery: createFilter(filterQuery), pagination: generateTablePaginationOptions(activePage, limit), @@ -197,7 +202,7 @@ export const useUncommonProcesses = ({ } return prevRequest; }); - }, [activePage, defaultIndex, docValueFields, endDate, filterQuery, limit, skip, startDate]); + }, [activePage, indexNames, docValueFields, endDate, filterQuery, limit, skip, startDate]); useEffect(() => { uncommonProcessesSearch(uncommonProcessesRequest); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx index 11a268c7b64ad..708c8b2b40b35 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx @@ -84,6 +84,7 @@ describe('body', () => { setQuery={jest.fn()} setAbsoluteRangeDatePicker={(jest.fn() as unknown) as SetAbsoluteRangeDatePicker} hostDetailsPagePath={hostDetailsPagePath} + indexNames={[]} indexPattern={mockIndexPattern} type={type} pageFilters={mockHostDetailsPageFilters} diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx index 4d4eead0e778a..284e6e27cf615 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx @@ -28,12 +28,13 @@ import { export const HostDetailsTabs = React.memo( ({ + detailName, docValueFields, - pageFilters, filterQuery, - detailName, - setAbsoluteRangeDatePicker, + indexNames, indexPattern, + pageFilters, + setAbsoluteRangeDatePicker, hostDetailsPagePath, }) => { const { from, to, isInitializing, deleteQuery, setQuery } = useGlobalTime(); @@ -73,6 +74,7 @@ export const HostDetailsTabs = React.memo( startDate: from, type, indexPattern, + indexNames, hostName: detailName, narrowDateRange, updateDateRange, diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx index d8cd59f119d52..55b2b529000be 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx @@ -21,15 +21,13 @@ import { hasMlUserPermissions } from '../../../../common/machine_learning/has_ml import { useMlCapabilities } from '../../../common/components/ml/hooks/use_ml_capabilities'; import { scoreIntervalToDateTime } from '../../../common/components/ml/score/score_interval_to_datetime'; import { SiemNavigation } from '../../../common/components/navigation'; -import { KpiHostsComponent } from '../../components/kpi_hosts'; +import { HostsDetailsKpiComponent } from '../../components/kpi_hosts'; import { HostOverview } from '../../../overview/components/host_overview'; import { manageQuery } from '../../../common/components/page/manage_query'; import { SiemSearchBar } from '../../../common/components/search_bar'; import { WrapperPage } from '../../../common/components/wrapper_page'; import { HostOverviewByNameQuery } from '../../containers/hosts/details'; -import { KpiHostDetailsQuery } from '../../containers/kpi_host_details'; import { useGlobalTime } from '../../../common/containers/use_global_time'; -import { useWithSource } from '../../../common/containers/source'; import { LastEventIndexKey } from '../../../graphql/types'; import { useKibana } from '../../../common/lib/kibana'; import { convertToBuildEsQuery } from '../../../common/lib/keury'; @@ -52,9 +50,9 @@ import { timelineSelectors } from '../../../timelines/store/timeline'; import { TimelineModel } from '../../../timelines/store/timeline/model'; import { TimelineId } from '../../../../common/types/timeline'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; +import { useSourcererScope } from '../../../common/containers/sourcerer'; const HostOverviewManage = manageQuery(HostOverview); -const KpiHostDetailsManage = manageQuery(KpiHostsComponent); const HostDetailsComponent = React.memo( ({ @@ -91,7 +89,7 @@ const HostDetailsComponent = React.memo( }, [setAbsoluteRangeDatePicker] ); - const { docValueFields, indicesExist, indexPattern } = useWithSource(); + const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope(); const filterQuery = convertToBuildEsQuery({ config: esQuery.getEsQueryConfig(kibana.services.uiSettings), indexPattern, @@ -113,12 +111,18 @@ const HostDetailsComponent = React.memo( + } title={detailName} /> ( > {({ isLoadingAnomaliesData, anomaliesData }) => ( ( data={hostOverview as HostItem} anomaliesData={anomaliesData} isLoadingAnomaliesData={isLoadingAnomaliesData} + indexNames={selectedPatterns} loading={loading} startDate={from} endDate={to} @@ -160,27 +166,15 @@ const HostDetailsComponent = React.memo( - - {({ kpiHostDetails, id, inspect, loading, refetch }) => ( - - )} - + /> @@ -193,6 +187,7 @@ const HostDetailsComponent = React.memo( ; export type HostDetailsTabsProps = HostBodyComponentDispatchProps & HostsQueryProps & { docValueFields?: DocValueFields[]; + indexNames: string[]; pageFilters?: Filter[]; filterQuery: string; indexPattern: IIndexPattern; diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx index 566f8f23efd39..b341647afdfbc 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx @@ -10,7 +10,6 @@ import { Router } from 'react-router-dom'; import { Filter } from '../../../../../../src/plugins/data/common/es_query'; import '../../common/mock/match_media'; -import { useWithSource } from '../../common/containers/source'; import { apolloClientObservable, TestProviders, @@ -25,8 +24,9 @@ import { State, createStore } from '../../common/store'; import { HostsComponentProps } from './types'; import { Hosts } from './hosts'; import { HostsTabs } from './hosts_tabs'; +import { useSourcererScope } from '../../common/containers/sourcerer'; -jest.mock('../../common/containers/source'); +jest.mock('../../common/containers/sourcerer'); // Test will fail because we will to need to mock some core services to make the test work // For now let's forget about SiemSearchBar and QueryBar @@ -58,14 +58,14 @@ const mockHistory = { createHref: jest.fn(), listen: jest.fn(), }; - +const mockUseSourcererScope = useSourcererScope as jest.Mock; describe('Hosts - rendering', () => { const hostProps: HostsComponentProps = { hostsPagePath: '', }; test('it renders the Setup Instructions text when no index is available', async () => { - (useWithSource as jest.Mock).mockReturnValue({ + mockUseSourcererScope.mockReturnValue({ indicesExist: false, }); @@ -80,7 +80,7 @@ describe('Hosts - rendering', () => { }); test('it DOES NOT render the Setup Instructions text when an index is available', async () => { - (useWithSource as jest.Mock).mockReturnValue({ + mockUseSourcererScope.mockReturnValue({ indicesExist: true, indexPattern: {}, }); @@ -95,7 +95,7 @@ describe('Hosts - rendering', () => { }); test('it should render tab navigation', async () => { - (useWithSource as jest.Mock).mockReturnValue({ + mockUseSourcererScope.mockReturnValue({ indicesExist: true, indexPattern: {}, }); @@ -142,7 +142,7 @@ describe('Hosts - rendering', () => { }, }, ]; - (useWithSource as jest.Mock).mockReturnValue({ + mockUseSourcererScope.mockReturnValue({ indicesExist: true, indexPattern: { fields: [], title: 'title' }, }); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx index ef88c255b1735..ea8cf11e7595a 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx @@ -17,14 +17,11 @@ import { HeaderPage } from '../../common/components/header_page'; import { LastEventTime } from '../../common/components/last_event_time'; import { hasMlUserPermissions } from '../../../common/machine_learning/has_ml_user_permissions'; import { SiemNavigation } from '../../common/components/navigation'; -import { KpiHostsComponent } from '../components/kpi_hosts'; -import { manageQuery } from '../../common/components/page/manage_query'; +import { HostsKpiComponent } from '../components/kpi_hosts'; import { SiemSearchBar } from '../../common/components/search_bar'; import { WrapperPage } from '../../common/components/wrapper_page'; -import { KpiHostsQuery } from '../containers/kpi_hosts'; import { useFullScreen } from '../../common/containers/use_full_screen'; import { useGlobalTime } from '../../common/containers/use_global_time'; -import { useWithSource } from '../../common/containers/source'; import { TimelineId } from '../../../common/types/timeline'; import { LastEventIndexKey } from '../../graphql/types'; import { useKibana } from '../../common/lib/kibana'; @@ -48,8 +45,7 @@ import { showGlobalFilters } from '../../timelines/components/timeline/helpers'; import { timelineSelectors } from '../../timelines/store/timeline'; import { timelineDefaults } from '../../timelines/store/timeline/defaults'; import { TimelineModel } from '../../timelines/store/timeline/model'; - -const KpiHostsComponentManage = manageQuery(KpiHostsComponent); +import { useSourcererScope } from '../../common/containers/sourcerer'; export const HostsComponent = React.memo( ({ filters, graphEventId, query, setAbsoluteRangeDatePicker, hostsPagePath }) => { @@ -78,7 +74,7 @@ export const HostsComponent = React.memo( }, [setAbsoluteRangeDatePicker] ); - const { docValueFields, indicesExist, indexPattern } = useWithSource(); + const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope(); const filterQuery = convertToBuildEsQuery({ config: esQuery.getEsQueryConfig(kibana.services.uiSettings), indexPattern, @@ -105,31 +101,25 @@ export const HostsComponent = React.memo( } + subtitle={ + + } title={i18n.PAGE_TITLE} /> - - {({ kpiHosts, loading, id, inspect, refetch }) => ( - - )} - + narrowDateRange={narrowDateRange} + /> @@ -144,11 +134,11 @@ export const HostsComponent = React.memo( to={to} filterQuery={tabsFilterQuery} isInitializing={isInitializing} + indexNames={selectedPatterns} setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker} setQuery={setQuery} from={from} type={hostsModel.HostsType.page} - indexPattern={indexPattern} hostsPagePath={hostsPagePath} /> diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx index 8e2ea06fd20cb..17dd20bac2d0d 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx @@ -28,24 +28,24 @@ export const HostsTabs = memo( deleteQuery, docValueFields, filterQuery, - setAbsoluteRangeDatePicker, - to, from, - setQuery, + indexNames, isInitializing, - type, - indexPattern, hostsPagePath, + setAbsoluteRangeDatePicker, + setQuery, + to, + type, }) => { const tabProps = { deleteQuery, endDate: to, filterQuery, + indexNames, skip: isInitializing, setQuery, startDate: from, type, - indexPattern, narrowDateRange: useCallback( (score: Anomaly, interval: string) => { const fromTo = scoreIntervalToDateTime(score, interval); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx index 65ddb9305f607..efce312fd85f2 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx @@ -16,7 +16,7 @@ import { MatrixHistogramConfigs, } from '../../../common/components/matrix_histogram/types'; import { MatrixHistogram } from '../../../common/components/matrix_histogram'; -import { KpiHostsChartColors } from '../../components/kpi_hosts/types'; +import { HostsKpiChartColors } from '../../components/kpi_hosts/types'; import * as i18n from '../translations'; import { MatrixHistogramType } from '../../../../common/search_strategy/security_solution'; @@ -24,7 +24,7 @@ const AuthenticationTableManage = manageQuery(AuthenticationTable); const ID = 'authenticationsHistogramQuery'; -const authStackByOptions: MatrixHistogramOption[] = [ +const authenticationsStackByOptions: MatrixHistogramOption[] = [ { text: 'event.outcome', value: 'event.outcome', @@ -32,31 +32,32 @@ const authStackByOptions: MatrixHistogramOption[] = [ ]; const DEFAULT_STACK_BY = 'event.outcome'; -enum AuthMatrixDataGroup { - authSuccess = 'success', - authFailure = 'failure', +enum AuthenticationsMatrixDataGroup { + authenticationsSuccess = 'success', + authenticationsFailure = 'failure', } -export const authMatrixDataMappingFields: MatrixHistogramMappingTypes = { - [AuthMatrixDataGroup.authSuccess]: { - key: AuthMatrixDataGroup.authSuccess, +export const authenticationsMatrixDataMappingFields: MatrixHistogramMappingTypes = { + [AuthenticationsMatrixDataGroup.authenticationsSuccess]: { + key: AuthenticationsMatrixDataGroup.authenticationsSuccess, value: null, - color: KpiHostsChartColors.authSuccess, + color: HostsKpiChartColors.authenticationsSuccess, }, - [AuthMatrixDataGroup.authFailure]: { - key: AuthMatrixDataGroup.authFailure, + [AuthenticationsMatrixDataGroup.authenticationsFailure]: { + key: AuthenticationsMatrixDataGroup.authenticationsFailure, value: null, - color: KpiHostsChartColors.authFailure, + color: HostsKpiChartColors.authenticationsFailure, }, }; const histogramConfigs: MatrixHistogramConfigs = { defaultStackByOption: - authStackByOptions.find((o) => o.text === DEFAULT_STACK_BY) ?? authStackByOptions[0], + authenticationsStackByOptions.find((o) => o.text === DEFAULT_STACK_BY) ?? + authenticationsStackByOptions[0], errorMessage: i18n.ERROR_FETCHING_AUTHENTICATIONS_DATA, histogramType: MatrixHistogramType.authentications, - mapping: authMatrixDataMappingFields, - stackByOptions: authStackByOptions, + mapping: authenticationsMatrixDataMappingFields, + stackByOptions: authenticationsStackByOptions, title: i18n.NAVIGATION_AUTHENTICATIONS_TITLE, }; @@ -65,6 +66,7 @@ const AuthenticationsQueryTabBodyComponent: React.FC docValueFields, endDate, filterQuery, + indexNames, skip, setQuery, startDate, @@ -73,7 +75,15 @@ const AuthenticationsQueryTabBodyComponent: React.FC const [ loading, { authentications, totalCount, pageInfo, loadPage, id, inspect, isInspected, refetch }, - ] = useAuthentications({ docValueFields, endDate, filterQuery, skip, startDate, type }); + ] = useAuthentications({ + docValueFields, + endDate, + filterQuery, + indexNames, + skip, + startDate, + type, + }); useEffect(() => { return () => { @@ -89,6 +99,7 @@ const AuthenticationsQueryTabBodyComponent: React.FC endDate={endDate} filterQuery={filterQuery} id={ID} + indexNames={indexNames} setQuery={setQuery} startDate={startDate} {...histogramConfigs} diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx index be8412caf7732..e30071ec04f0c 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx @@ -20,6 +20,7 @@ import { useFullScreen } from '../../../common/containers/use_full_screen'; import * as i18n from '../translations'; import { MatrixHistogramType } from '../../../../common/search_strategy/security_solution'; import { useManageTimeline } from '../../../timelines/components/manage_timeline'; +import { SourcererScopeName } from '../../../common/store/sourcerer/model'; const EVENTS_HISTOGRAM_ID = 'eventsHistogramQuery'; @@ -54,6 +55,7 @@ const EventsQueryTabBodyComponent: React.FC = ({ deleteQuery, endDate, filterQuery, + indexNames, pageFilters, setQuery, startDate, @@ -85,6 +87,7 @@ const EventsQueryTabBodyComponent: React.FC = ({ setQuery={setQuery} startDate={startDate} id={EVENTS_HISTOGRAM_ID} + indexNames={indexNames} {...histogramConfigs} /> )} @@ -92,6 +95,7 @@ const EventsQueryTabBodyComponent: React.FC = ({ defaultModel={eventsDefaultModel} end={endDate} id={TimelineId.hostsPageEvents} + scopeId={SourcererScopeName.default} start={startDate} pageFilters={pageFilters} /> diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/hosts_query_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/hosts_query_tab_body.tsx index f8dcf9635c053..deda4b618fa64 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/hosts_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/hosts_query_tab_body.tsx @@ -18,7 +18,7 @@ export const HostsQueryTabBody = ({ docValueFields, endDate, filterQuery, - indexPattern, + indexNames, skip, setQuery, startDate, @@ -27,7 +27,7 @@ export const HostsQueryTabBody = ({ const [ loading, { hosts, totalCount, pageInfo, loadPage, id, inspect, isInspected, refetch }, - ] = useAllHost({ docValueFields, endDate, filterQuery, skip, startDate, type }); + ] = useAllHost({ docValueFields, endDate, filterQuery, indexNames, skip, startDate, type }); return ( { })}${appendSearch(search)}`; }; -const isDefaultOrMissing = ( - value: number | string | undefined, - defaultValue: number | undefined -) => { +const isDefaultOrMissing = (value: T | undefined, defaultValue: T) => { return value === undefined || value === defaultValue; }; 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 372916581b35d..1d27c75de009d 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 @@ -35,7 +35,12 @@ export const AdministrationListPage: FC - + {actions} 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 235f150637116..40c982cfc071b 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 @@ -203,6 +203,7 @@ export const PolicyDetails = React.memo(() => { )} ; + deleteTrustedApp(request: DeleteTrustedAppsRequestParams): Promise; createTrustedApp(request: PostTrustedAppCreateRequest): Promise; } @@ -30,6 +37,10 @@ export class TrustedAppsHttpService implements TrustedAppsService { }); } + async deleteTrustedApp(request: DeleteTrustedAppsRequestParams): Promise { + return this.http.delete(resolvePathVariables(TRUSTED_APPS_DELETE_API, request)); + } + async createTrustedApp(request: PostTrustedAppCreateRequest) { return this.http.post(TRUSTED_APPS_CREATE_API, { body: JSON.stringify(request), diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/utils.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/utils.test.ts new file mode 100644 index 0000000000000..c937b318e8961 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/utils.test.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { resolvePathVariables } from './utils'; + +describe('utils', () => { + describe('resolvePathVariables', () => { + it('should resolve defined variables', () => { + expect(resolvePathVariables('/segment1/{var1}/segment2', { var1: 'value1' })).toBe( + '/segment1/value1/segment2' + ); + }); + + it('should not resolve undefined variables', () => { + expect(resolvePathVariables('/segment1/{var1}/segment2', {})).toBe( + '/segment1/{var1}/segment2' + ); + }); + + it('should ignore unused variables', () => { + expect(resolvePathVariables('/segment1/{var1}/segment2', { var2: 'value2' })).toBe( + '/segment1/{var1}/segment2' + ); + }); + + it('should replace multiple variable occurences', () => { + expect(resolvePathVariables('/{var1}/segment1/{var1}', { var1: 'value1' })).toBe( + '/value1/segment1/value1' + ); + }); + + it('should replace multiple variables', () => { + const path = resolvePathVariables('/{var1}/segment1/{var2}', { + var1: 'value1', + var2: 'value2', + }); + + expect(path).toBe('/value1/segment1/value2'); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/utils.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/utils.ts new file mode 100644 index 0000000000000..075d74da018b4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/utils.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const resolvePathVariables = (path: string, variables: { [K: string]: string | number }) => + Object.keys(variables).reduce((acc, paramName) => { + return acc.replace(new RegExp(`\{${paramName}\}`, 'g'), String(variables[paramName])); + }, path); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.test.ts index 5e00d833981ed..534a4ec14861b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.test.ts @@ -13,6 +13,7 @@ import { isLoadingResourceState, isLoadedResourceState, isFailedResourceState, + isStaleResourceState, getLastLoadedResourceState, getCurrentResourceError, isOutdatedResourceState, @@ -137,6 +138,24 @@ describe('AsyncResourceState', () => { expect(isFailedResourceState(failedResourceStateInitially)).toBe(true); }); }); + + describe('isStaleResourceState()', () => { + it('returns true for UninitialisedResourceState', () => { + expect(isStaleResourceState(uninitialisedResourceState)).toBe(true); + }); + + it('returns false for LoadingResourceState', () => { + expect(isStaleResourceState(loadingResourceStateInitially)).toBe(false); + }); + + it('returns true for LoadedResourceState', () => { + expect(isStaleResourceState(loadedResourceState)).toBe(true); + }); + + it('returns true for FailedResourceState', () => { + expect(isStaleResourceState(failedResourceStateInitially)).toBe(true); + }); + }); }); describe('functions', () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.ts index 4639a50a61865..bb868418e7f0d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.ts @@ -35,7 +35,7 @@ export interface UninitialisedResourceState { * @param Data - type of the data that is referenced by resource state * @param Error - type of the error that can happen during attempt to update data */ -export interface LoadingResourceState { +export interface LoadingResourceState { type: 'LoadingResourceState'; previousState: StaleResourceState; } @@ -46,7 +46,7 @@ export interface LoadingResourceState { * * @param Data - type of the data that is referenced by resource state */ -export interface LoadedResourceState { +export interface LoadedResourceState { type: 'LoadedResourceState'; data: Data; } @@ -59,7 +59,7 @@ export interface LoadedResourceState { * @param Data - type of the data that is referenced by resource state * @param Error - type of the error that can happen during attempt to update data */ -export interface FailedResourceState { +export interface FailedResourceState { type: 'FailedResourceState'; error: Error; lastLoadedState?: LoadedResourceState; @@ -71,7 +71,7 @@ export interface FailedResourceState { * @param Data - type of the data that is referenced by resource state * @param Error - type of the error that can happen during attempt to update data */ -export type StaleResourceState = +export type StaleResourceState = | UninitialisedResourceState | LoadedResourceState | FailedResourceState; @@ -82,7 +82,7 @@ export type StaleResourceState = * @param Data - type of the data that is referenced by resource state * @param Error - type of the error that can happen during attempt to update data */ -export type AsyncResourceState = +export type AsyncResourceState = | UninitialisedResourceState | LoadingResourceState | LoadedResourceState @@ -106,6 +106,13 @@ export const isFailedResourceState = ( state: Immutable> ): state is Immutable> => state.type === 'FailedResourceState'; +export const isStaleResourceState = ( + state: Immutable> +): state is Immutable> => + isUninitialisedResourceState(state) || + isLoadedResourceState(state) || + isFailedResourceState(state); + // Set of functions to work with AsyncResourceState export const getLastLoadedResourceState = ( diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts index 071557ec1a815..4c38ac0c4239a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ServerApiError } from '../../../../common/types'; import { NewTrustedApp, TrustedApp } from '../../../../../common/endpoint/types/trusted_apps'; import { AsyncResourceState } from '.'; import { TrustedAppsUrlParams } from '../types'; -import { ServerApiError } from '../../../../common/types'; export interface PaginationInfo { index: number; @@ -18,6 +18,7 @@ export interface TrustedAppsListData { items: TrustedApp[]; totalItemsCount: number; paginationInfo: PaginationInfo; + timestamp: number; } /** Store State when an API request has been sent to create a new trusted app entry */ @@ -42,8 +43,14 @@ export interface TrustedAppsListPageState { listView: { currentListResourceState: AsyncResourceState; currentPaginationInfo: PaginationInfo; + freshDataTimestamp: number; show: TrustedAppsUrlParams['show'] | undefined; }; + deletionDialog: { + entry?: TrustedApp; + confirmed: boolean; + submissionResourceState: AsyncResourceState; + }; createView: | undefined | TrustedAppCreatePending diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/action.ts index 3a43ffe58262c..5315087c09655 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/action.ts @@ -4,6 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Action } from 'redux'; + +import { TrustedApp } from '../../../../../common/endpoint/types'; import { AsyncResourceState, TrustedAppCreateFailure, @@ -12,12 +15,30 @@ import { TrustedAppsListData, } from '../state'; -export interface TrustedAppsListResourceStateChanged { - type: 'trustedAppsListResourceStateChanged'; +export type TrustedAppsListDataOutdated = Action<'trustedAppsListDataOutdated'>; + +interface ResourceStateChanged extends Action { + payload: { newState: AsyncResourceState }; +} + +export type TrustedAppsListResourceStateChanged = ResourceStateChanged< + 'trustedAppsListResourceStateChanged', + TrustedAppsListData +>; + +export type TrustedAppDeletionSubmissionResourceStateChanged = ResourceStateChanged< + 'trustedAppDeletionSubmissionResourceStateChanged' +>; + +export type TrustedAppDeletionDialogStarted = Action<'trustedAppDeletionDialogStarted'> & { payload: { - newState: AsyncResourceState; + entry: TrustedApp; }; -} +}; + +export type TrustedAppDeletionDialogConfirmed = Action<'trustedAppDeletionDialogConfirmed'>; + +export type TrustedAppDeletionDialogClosed = Action<'trustedAppDeletionDialogClosed'>; export interface UserClickedSaveNewTrustedAppButton { type: 'userClickedSaveNewTrustedAppButton'; @@ -35,7 +56,12 @@ export interface ServerReturnedCreateTrustedAppFailure { } export type TrustedAppsPageAction = + | TrustedAppsListDataOutdated | TrustedAppsListResourceStateChanged + | TrustedAppDeletionSubmissionResourceStateChanged + | TrustedAppDeletionDialogStarted + | TrustedAppDeletionDialogConfirmed + | TrustedAppDeletionDialogClosed | UserClickedSaveNewTrustedAppButton | ServerReturnedCreateTrustedAppSuccess | ServerReturnedCreateTrustedAppFailure; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts index e5f00ee0ccf81..19c2d3a62781f 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts @@ -10,8 +10,10 @@ import { createSpyMiddleware } from '../../../../common/store/test_utils'; import { createFailedListViewWithPagination, + createListLoadedResourceState, createLoadedListViewWithPagination, createLoadingListViewWithPagination, + createSampleTrustedApp, createSampleTrustedApps, createServerApiError, createUserChangedUrlAction, @@ -22,6 +24,14 @@ import { PaginationInfo, TrustedAppsListPageState } from '../state'; import { initialTrustedAppsPageState, trustedAppsPageReducer } from './reducer'; import { createTrustedAppsPageMiddleware } from './middleware'; +const initialNow = 111111; +const dateNowMock = jest.fn(); +dateNowMock.mockReturnValue(initialNow); + +Date.now = dateNowMock; + +const initialState = initialTrustedAppsPageState(); + const createGetTrustedListAppsResponse = (pagination: PaginationInfo, totalItemsCount: number) => ({ data: createSampleTrustedApps(pagination), page: pagination.index, @@ -31,6 +41,7 @@ const createGetTrustedListAppsResponse = (pagination: PaginationInfo, totalItems const createTrustedAppsServiceMock = (): jest.Mocked => ({ getTrustedAppsList: jest.fn(), + deleteTrustedApp: jest.fn(), createTrustedApp: jest.fn(), }); @@ -50,13 +61,19 @@ const createStoreSetup = (trustedAppsService: TrustedAppsService) => { }; describe('middleware', () => { - describe('refreshing list resource state', () => { + beforeEach(() => { + dateNowMock.mockReturnValue(initialNow); + }); + + describe('initial state', () => { it('sets initial state properly', async () => { expect(createStoreSetup(createTrustedAppsServiceMock()).store.getState()).toStrictEqual( - initialTrustedAppsPageState + initialState ); }); + }); + describe('refreshing list resource state', () => { it('refreshes the list when location changes and data gets outdated', async () => { const pagination = { index: 2, size: 50 }; const service = createTrustedAppsServiceMock(); @@ -69,17 +86,17 @@ describe('middleware', () => { store.dispatch(createUserChangedUrlAction('/trusted_apps', '?page_index=2&page_size=50')); expect(store.getState()).toStrictEqual({ - listView: createLoadingListViewWithPagination(pagination), + ...initialState, + listView: createLoadingListViewWithPagination(initialNow, pagination), active: true, - createView: undefined, }); await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); expect(store.getState()).toStrictEqual({ - listView: createLoadedListViewWithPagination(pagination, pagination, 500), + ...initialState, + listView: createLoadedListViewWithPagination(initialNow, pagination, pagination, 500), active: true, - createView: undefined, }); }); @@ -100,13 +117,50 @@ describe('middleware', () => { expect(service.getTrustedAppsList).toBeCalledTimes(1); expect(store.getState()).toStrictEqual({ - listView: createLoadedListViewWithPagination(pagination, pagination, 500), + ...initialState, + listView: createLoadedListViewWithPagination(initialNow, pagination, pagination, 500), active: true, - createView: undefined, }); }); - it('set list resource state to faile when failing to load data', async () => { + it('refreshes the list when data gets outdated with and outdate action', async () => { + const newNow = 222222; + const pagination = { index: 0, size: 10 }; + const service = createTrustedAppsServiceMock(); + const { store, spyMiddleware } = createStoreSetup(service); + + service.getTrustedAppsList.mockResolvedValue( + createGetTrustedListAppsResponse(pagination, 500) + ); + + store.dispatch(createUserChangedUrlAction('/trusted_apps')); + + await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); + + dateNowMock.mockReturnValue(newNow); + + store.dispatch({ type: 'trustedAppsListDataOutdated' }); + + expect(store.getState()).toStrictEqual({ + ...initialState, + listView: createLoadingListViewWithPagination( + newNow, + pagination, + createListLoadedResourceState(pagination, 500, initialNow) + ), + active: true, + }); + + await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); + + expect(store.getState()).toStrictEqual({ + ...initialState, + listView: createLoadedListViewWithPagination(newNow, pagination, pagination, 500), + active: true, + }); + }); + + it('set list resource state to failed when failing to load data', async () => { const service = createTrustedAppsServiceMock(); const { store, spyMiddleware } = createStoreSetup(service); @@ -117,12 +171,13 @@ describe('middleware', () => { await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); expect(store.getState()).toStrictEqual({ + ...initialState, listView: createFailedListViewWithPagination( + initialNow, { index: 2, size: 50 }, createServerApiError('Internal Server Error') ), active: true, - createView: undefined, }); const infiniteLoopTest = async () => { @@ -132,4 +187,151 @@ describe('middleware', () => { await expect(infiniteLoopTest).rejects.not.toBeNull(); }); }); + + describe('submitting deletion dialog', () => { + const newNow = 222222; + const entry = createSampleTrustedApp(3); + const notFoundError = createServerApiError('Not Found'); + const pagination = { index: 0, size: 10 }; + const getTrustedAppsListResponse = createGetTrustedListAppsResponse(pagination, 500); + const listView = createLoadedListViewWithPagination(initialNow, pagination, pagination, 500); + const listViewNew = createLoadedListViewWithPagination(newNow, pagination, pagination, 500); + const testStartState = { ...initialState, listView, active: true }; + + it('does not submit when entry is undefined', async () => { + const service = createTrustedAppsServiceMock(); + const { store, spyMiddleware } = createStoreSetup(service); + + service.getTrustedAppsList.mockResolvedValue(getTrustedAppsListResponse); + service.deleteTrustedApp.mockResolvedValue(); + + store.dispatch(createUserChangedUrlAction('/trusted_apps')); + + await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); + + store.dispatch({ type: 'trustedAppDeletionDialogConfirmed' }); + + expect(store.getState()).toStrictEqual({ + ...testStartState, + deletionDialog: { ...testStartState.deletionDialog, confirmed: true }, + }); + }); + + it('submits successfully when entry is defined', async () => { + const service = createTrustedAppsServiceMock(); + const { store, spyMiddleware } = createStoreSetup(service); + + service.getTrustedAppsList.mockResolvedValue(getTrustedAppsListResponse); + service.deleteTrustedApp.mockResolvedValue(); + + store.dispatch(createUserChangedUrlAction('/trusted_apps')); + + await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); + + dateNowMock.mockReturnValue(newNow); + + store.dispatch({ type: 'trustedAppDeletionDialogStarted', payload: { entry } }); + store.dispatch({ type: 'trustedAppDeletionDialogConfirmed' }); + + expect(store.getState()).toStrictEqual({ + ...testStartState, + deletionDialog: { + entry, + confirmed: true, + submissionResourceState: { + type: 'LoadingResourceState', + previousState: { type: 'UninitialisedResourceState' }, + }, + }, + }); + + await spyMiddleware.waitForAction('trustedAppDeletionSubmissionResourceStateChanged'); + await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); + + expect(store.getState()).toStrictEqual({ ...testStartState, listView: listViewNew }); + expect(service.deleteTrustedApp).toBeCalledWith({ id: '3' }); + expect(service.deleteTrustedApp).toBeCalledTimes(1); + }); + + it('does not submit twice', async () => { + const service = createTrustedAppsServiceMock(); + const { store, spyMiddleware } = createStoreSetup(service); + + service.getTrustedAppsList.mockResolvedValue(getTrustedAppsListResponse); + service.deleteTrustedApp.mockResolvedValue(); + + store.dispatch(createUserChangedUrlAction('/trusted_apps')); + + await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); + + dateNowMock.mockReturnValue(newNow); + + store.dispatch({ type: 'trustedAppDeletionDialogStarted', payload: { entry } }); + store.dispatch({ type: 'trustedAppDeletionDialogConfirmed' }); + store.dispatch({ type: 'trustedAppDeletionDialogConfirmed' }); + + expect(store.getState()).toStrictEqual({ + ...testStartState, + deletionDialog: { + entry, + confirmed: true, + submissionResourceState: { + type: 'LoadingResourceState', + previousState: { type: 'UninitialisedResourceState' }, + }, + }, + }); + + await spyMiddleware.waitForAction('trustedAppDeletionSubmissionResourceStateChanged'); + await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); + + expect(store.getState()).toStrictEqual({ ...testStartState, listView: listViewNew }); + expect(service.deleteTrustedApp).toBeCalledWith({ id: '3' }); + expect(service.deleteTrustedApp).toBeCalledTimes(1); + }); + + it('does not submit when server response with failure', async () => { + const service = createTrustedAppsServiceMock(); + const { store, spyMiddleware } = createStoreSetup(service); + + service.getTrustedAppsList.mockResolvedValue(getTrustedAppsListResponse); + service.deleteTrustedApp.mockRejectedValue(notFoundError); + + store.dispatch(createUserChangedUrlAction('/trusted_apps')); + + await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); + + store.dispatch({ type: 'trustedAppDeletionDialogStarted', payload: { entry } }); + store.dispatch({ type: 'trustedAppDeletionDialogConfirmed' }); + + expect(store.getState()).toStrictEqual({ + ...testStartState, + deletionDialog: { + entry, + confirmed: true, + submissionResourceState: { + type: 'LoadingResourceState', + previousState: { type: 'UninitialisedResourceState' }, + }, + }, + }); + + await spyMiddleware.waitForAction('trustedAppDeletionSubmissionResourceStateChanged'); + + expect(store.getState()).toStrictEqual({ + ...testStartState, + deletionDialog: { + entry, + confirmed: true, + submissionResourceState: { + type: 'FailedResourceState', + error: notFoundError, + lastLoadedState: undefined, + }, + }, + }); + expect(service.deleteTrustedApp).toBeCalledWith({ id: '3' }); + expect(service.deleteTrustedApp).toBeCalledTimes(1); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts index bf9cacff5caf0..dd96c8d807048 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts @@ -16,15 +16,22 @@ import { TrustedAppsHttpService, TrustedAppsService } from '../service'; import { AsyncResourceState, + getLastLoadedResourceState, + isStaleResourceState, StaleResourceState, TrustedAppsListData, TrustedAppsListPageState, } from '../state'; -import { TrustedAppsListResourceStateChanged } from './action'; +import { + TrustedAppDeletionSubmissionResourceStateChanged, + TrustedAppsListResourceStateChanged, +} from './action'; import { getCurrentListResourceState, + getDeletionDialogEntry, + getDeletionSubmissionResourceState, getLastLoadedListResourceState, getListCurrentPageIndex, getListCurrentPageSize, @@ -40,46 +47,98 @@ const createTrustedAppsListResourceStateChangedAction = ( payload: { newState }, }); -const refreshList = async ( +const refreshListIfNeeded = async ( store: ImmutableMiddlewareAPI, trustedAppsService: TrustedAppsService ) => { - store.dispatch( - createTrustedAppsListResourceStateChangedAction({ - type: 'LoadingResourceState', - // need to think on how to avoid the casting - previousState: getCurrentListResourceState(store.getState()) as Immutable< - StaleResourceState - >, - }) - ); - - try { - const pageIndex = getListCurrentPageIndex(store.getState()); - const pageSize = getListCurrentPageSize(store.getState()); - const response = await trustedAppsService.getTrustedAppsList({ - page: pageIndex + 1, - per_page: pageSize, - }); - + if (needsRefreshOfListData(store.getState())) { store.dispatch( createTrustedAppsListResourceStateChangedAction({ - type: 'LoadedResourceState', - data: { - items: response.data, - totalItemsCount: response.total, - paginationInfo: { index: pageIndex, size: pageSize }, - }, + type: 'LoadingResourceState', + // need to think on how to avoid the casting + previousState: getCurrentListResourceState(store.getState()) as Immutable< + StaleResourceState + >, }) ); - } catch (error) { + + try { + const pageIndex = getListCurrentPageIndex(store.getState()); + const pageSize = getListCurrentPageSize(store.getState()); + const response = await trustedAppsService.getTrustedAppsList({ + page: pageIndex + 1, + per_page: pageSize, + }); + + store.dispatch( + createTrustedAppsListResourceStateChangedAction({ + type: 'LoadedResourceState', + data: { + items: response.data, + totalItemsCount: response.total, + paginationInfo: { index: pageIndex, size: pageSize }, + timestamp: Date.now(), + }, + }) + ); + } catch (error) { + store.dispatch( + createTrustedAppsListResourceStateChangedAction({ + type: 'FailedResourceState', + error, + lastLoadedState: getLastLoadedListResourceState(store.getState()), + }) + ); + } + } +}; + +const createTrustedAppDeletionSubmissionResourceStateChanged = ( + newState: Immutable +): Immutable => ({ + type: 'trustedAppDeletionSubmissionResourceStateChanged', + payload: { newState }, +}); + +const submitDeletionIfNeeded = async ( + store: ImmutableMiddlewareAPI, + trustedAppsService: TrustedAppsService +) => { + const submissionResourceState = getDeletionSubmissionResourceState(store.getState()); + const entry = getDeletionDialogEntry(store.getState()); + + if (isStaleResourceState(submissionResourceState) && entry !== undefined) { store.dispatch( - createTrustedAppsListResourceStateChangedAction({ - type: 'FailedResourceState', - error, - lastLoadedState: getLastLoadedListResourceState(store.getState()), + createTrustedAppDeletionSubmissionResourceStateChanged({ + type: 'LoadingResourceState', + previousState: submissionResourceState, }) ); + + try { + await trustedAppsService.deleteTrustedApp({ id: entry.id }); + + store.dispatch( + createTrustedAppDeletionSubmissionResourceStateChanged({ + type: 'LoadedResourceState', + data: null, + }) + ); + store.dispatch({ + type: 'trustedAppDeletionDialogClosed', + }); + store.dispatch({ + type: 'trustedAppsListDataOutdated', + }); + } catch (error) { + store.dispatch( + createTrustedAppDeletionSubmissionResourceStateChanged({ + type: 'FailedResourceState', + error, + lastLoadedState: getLastLoadedResourceState(submissionResourceState), + }) + ); + } } }; @@ -102,7 +161,9 @@ const createTrustedApp = async ( data: createdTrustedApp, }, }); - refreshList(store, trustedAppsService); + store.dispatch({ + type: 'trustedAppsListDataOutdated', + }); } catch (error) { dispatch({ type: 'serverReturnedCreateTrustedAppFailure', @@ -122,8 +183,12 @@ export const createTrustedAppsPageMiddleware = ( next(action); // TODO: need to think if failed state is a good condition to consider need for refresh - if (action.type === 'userChangedUrl' && needsRefreshOfListData(store.getState())) { - await refreshList(store, trustedAppsService); + if (action.type === 'userChangedUrl' || action.type === 'trustedAppsListDataOutdated') { + await refreshListIfNeeded(store, trustedAppsService); + } + + if (action.type === 'trustedAppDeletionDialogConfirmed') { + await submitDeletionIfNeeded(store, trustedAppsService); } if (action.type === 'userClickedSaveNewTrustedAppButton') { diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts index 76dd4b48e63d2..228f0932edd28 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts @@ -4,93 +4,180 @@ * you may not use this file except in compliance with the Elastic License. */ +import { AsyncResourceState } from '../state'; import { initialTrustedAppsPageState, trustedAppsPageReducer } from './reducer'; import { + createSampleTrustedApp, createListLoadedResourceState, createLoadedListViewWithPagination, - createTrustedAppsListResourceStateChangedAction, createUserChangedUrlAction, + createTrustedAppsListResourceStateChangedAction, } from '../test_utils'; +const initialNow = 111111; +const dateNowMock = jest.fn(); +dateNowMock.mockReturnValue(initialNow); + +Date.now = dateNowMock; + +const initialState = initialTrustedAppsPageState(); + describe('reducer', () => { describe('UserChangedUrl', () => { it('makes page state active and extracts pagination parameters', () => { const result = trustedAppsPageReducer( - initialTrustedAppsPageState, + initialState, createUserChangedUrlAction('/trusted_apps', '?page_index=5&page_size=50') ); expect(result).toStrictEqual({ - listView: { - ...initialTrustedAppsPageState.listView, - currentPaginationInfo: { index: 5, size: 50 }, - }, + ...initialState, + listView: { ...initialState.listView, currentPaginationInfo: { index: 5, size: 50 } }, active: true, - createView: undefined, }); }); it('extracts default pagination parameters when none provided', () => { const result = trustedAppsPageReducer( { - ...initialTrustedAppsPageState, - listView: { - ...initialTrustedAppsPageState.listView, - currentPaginationInfo: { index: 5, size: 50 }, - }, + ...initialState, + listView: { ...initialState.listView, currentPaginationInfo: { index: 5, size: 50 } }, }, createUserChangedUrlAction('/trusted_apps', '?page_index=b&page_size=60') ); - expect(result).toStrictEqual({ - ...initialTrustedAppsPageState, - active: true, - }); + expect(result).toStrictEqual({ ...initialState, active: true }); }); it('extracts default pagination parameters when invalid provided', () => { const result = trustedAppsPageReducer( { - ...initialTrustedAppsPageState, - listView: { - ...initialTrustedAppsPageState.listView, - currentPaginationInfo: { index: 5, size: 50 }, - }, + ...initialState, + listView: { ...initialState.listView, currentPaginationInfo: { index: 5, size: 50 } }, }, createUserChangedUrlAction('/trusted_apps') ); - expect(result).toStrictEqual({ - ...initialTrustedAppsPageState, - active: true, - }); + expect(result).toStrictEqual({ ...initialState, active: true }); }); it('makes page state inactive and resets list to uninitialised state when navigating away', () => { const result = trustedAppsPageReducer( - { listView: createLoadedListViewWithPagination(), active: true, createView: undefined }, + { ...initialState, listView: createLoadedListViewWithPagination(initialNow), active: true }, createUserChangedUrlAction('/endpoints') ); - expect(result).toStrictEqual(initialTrustedAppsPageState); + expect(result).toStrictEqual(initialState); }); }); describe('TrustedAppsListResourceStateChanged', () => { it('sets the current list resource state', () => { - const listResourceState = createListLoadedResourceState({ index: 3, size: 50 }, 200); + const listResourceState = createListLoadedResourceState( + { index: 3, size: 50 }, + 200, + initialNow + ); const result = trustedAppsPageReducer( - initialTrustedAppsPageState, + initialState, createTrustedAppsListResourceStateChangedAction(listResourceState) ); expect(result).toStrictEqual({ - ...initialTrustedAppsPageState, - listView: { - ...initialTrustedAppsPageState.listView, - currentListResourceState: listResourceState, + ...initialState, + listView: { ...initialState.listView, currentListResourceState: listResourceState }, + }); + }); + }); + + describe('TrustedAppsListDataOutdated', () => { + it('sets the list view freshness timestamp', () => { + const newNow = 222222; + dateNowMock.mockReturnValue(newNow); + + const result = trustedAppsPageReducer(initialState, { type: 'trustedAppsListDataOutdated' }); + + expect(result).toStrictEqual({ + ...initialState, + listView: { ...initialState.listView, freshDataTimestamp: newNow }, + }); + }); + }); + + describe('TrustedAppDeletionSubmissionResourceStateChanged', () => { + it('sets the deletion dialog submission resource state', () => { + const submissionResourceState: AsyncResourceState = { + type: 'LoadedResourceState', + data: null, + }; + const result = trustedAppsPageReducer(initialState, { + type: 'trustedAppDeletionSubmissionResourceStateChanged', + payload: { newState: submissionResourceState }, + }); + + expect(result).toStrictEqual({ + ...initialState, + deletionDialog: { ...initialState.deletionDialog, submissionResourceState }, + }); + }); + }); + + describe('TrustedAppDeletionDialogStarted', () => { + it('sets the deletion dialog state to started', () => { + const entry = createSampleTrustedApp(3); + const result = trustedAppsPageReducer(initialState, { + type: 'trustedAppDeletionDialogStarted', + payload: { entry }, + }); + + expect(result).toStrictEqual({ + ...initialState, + deletionDialog: { ...initialState.deletionDialog, entry }, + }); + }); + }); + + describe('TrustedAppDeletionDialogConfirmed', () => { + it('sets the deletion dialog state to confirmed', () => { + const entry = createSampleTrustedApp(3); + const result = trustedAppsPageReducer( + { + ...initialState, + deletionDialog: { + entry, + confirmed: false, + submissionResourceState: { type: 'UninitialisedResourceState' }, + }, + }, + { type: 'trustedAppDeletionDialogConfirmed' } + ); + + expect(result).toStrictEqual({ + ...initialState, + deletionDialog: { + entry, + confirmed: true, + submissionResourceState: { type: 'UninitialisedResourceState' }, }, }); }); }); + + describe('TrustedAppDeletionDialogClosed', () => { + it('sets the deletion dialog state to confirmed', () => { + const result = trustedAppsPageReducer( + { + ...initialState, + deletionDialog: { + entry: createSampleTrustedApp(3), + confirmed: true, + submissionResourceState: { type: 'UninitialisedResourceState' }, + }, + }, + { type: 'trustedAppDeletionDialogClosed' } + ); + + expect(result).toStrictEqual(initialState); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.ts index d824a6e95c8d5..ec210254bf76f 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.ts @@ -19,9 +19,14 @@ import { } from '../../../common/constants'; import { + TrustedAppDeletionDialogClosed, + TrustedAppDeletionDialogConfirmed, + TrustedAppDeletionDialogStarted, + TrustedAppDeletionSubmissionResourceStateChanged, + TrustedAppsListDataOutdated, + TrustedAppsListResourceStateChanged, ServerReturnedCreateTrustedAppFailure, ServerReturnedCreateTrustedAppSuccess, - TrustedAppsListResourceStateChanged, UserClickedSaveNewTrustedAppButton, } from './action'; import { TrustedAppsListPageState } from '../state'; @@ -41,6 +46,16 @@ const isTrustedAppsPageLocation = (location: Immutable) => { ); }; +const trustedAppsListDataOutdated: CaseReducer = (state, action) => { + return { + ...state, + listView: { + ...state.listView, + freshDataTimestamp: Date.now(), + }, + }; +}; + const trustedAppsListResourceStateChanged: CaseReducer = ( state, action @@ -54,6 +69,44 @@ const trustedAppsListResourceStateChanged: CaseReducer = ( + state, + action +) => { + return { + ...state, + deletionDialog: { ...state.deletionDialog, submissionResourceState: action.payload.newState }, + }; +}; + +const trustedAppDeletionDialogStarted: CaseReducer = ( + state, + action +) => { + return { + ...state, + deletionDialog: { + entry: action.payload.entry, + confirmed: false, + submissionResourceState: { type: 'UninitialisedResourceState' }, + }, + }; +}; + +const trustedAppDeletionDialogConfirmed: CaseReducer = ( + state, + action +) => { + return { ...state, deletionDialog: { ...state.deletionDialog, confirmed: true } }; +}; + +const trustedAppDeletionDialogClosed: CaseReducer = ( + state, + action +) => { + return { ...state, deletionDialog: initialDeletionDialogState() }; +}; + const userChangedUrl: CaseReducer = (state, action) => { if (isTrustedAppsPageLocation(action.payload)) { const parsedUrlsParams = parse(action.payload.search.slice(1)); @@ -75,7 +128,7 @@ const userChangedUrl: CaseReducer = (state, action) => { active: true, }; } else { - return initialTrustedAppsPageState; + return initialTrustedAppsPageState(); } }; @@ -90,27 +143,49 @@ const trustedAppsCreateResourceChanged: CaseReducer< }; }; -export const initialTrustedAppsPageState: TrustedAppsListPageState = { +const initialDeletionDialogState = (): TrustedAppsListPageState['deletionDialog'] => ({ + confirmed: false, + submissionResourceState: { type: 'UninitialisedResourceState' }, +}); + +export const initialTrustedAppsPageState = (): TrustedAppsListPageState => ({ listView: { currentListResourceState: { type: 'UninitialisedResourceState' }, currentPaginationInfo: { index: MANAGEMENT_DEFAULT_PAGE, size: MANAGEMENT_DEFAULT_PAGE_SIZE, }, + freshDataTimestamp: Date.now(), show: undefined, }, + deletionDialog: initialDeletionDialogState(), createView: undefined, active: false, -}; +}); export const trustedAppsPageReducer: StateReducer = ( - state = initialTrustedAppsPageState, + state = initialTrustedAppsPageState(), action ) => { switch (action.type) { + case 'trustedAppsListDataOutdated': + return trustedAppsListDataOutdated(state, action); + case 'trustedAppsListResourceStateChanged': return trustedAppsListResourceStateChanged(state, action); + case 'trustedAppDeletionSubmissionResourceStateChanged': + return trustedAppDeletionSubmissionResourceStateChanged(state, action); + + case 'trustedAppDeletionDialogStarted': + return trustedAppDeletionDialogStarted(state, action); + + case 'trustedAppDeletionDialogConfirmed': + return trustedAppDeletionDialogConfirmed(state, action); + + case 'trustedAppDeletionDialogClosed': + return trustedAppDeletionDialogClosed(state, action); + case 'userChangedUrl': return userChangedUrl(state, action); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.test.ts index 453afa1befa6b..0be4d0b05acc4 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.test.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { AsyncResourceState, TrustedAppsListPageState } from '../state'; +import { initialTrustedAppsPageState } from './reducer'; import { getCurrentListResourceState, getLastLoadedListResourceState, @@ -14,6 +16,12 @@ import { getListTotalItemsCount, isListLoading, needsRefreshOfListData, + isDeletionDialogOpen, + isDeletionInProgress, + isDeletionSuccessful, + getDeletionError, + getDeletionDialogEntry, + getDeletionSubmissionResourceState, } from './selectors'; import { @@ -23,96 +31,118 @@ import { createListFailedResourceState, createListLoadedResourceState, createLoadedListViewWithPagination, + createSampleTrustedApp, createSampleTrustedApps, + createServerApiError, createUninitialisedResourceState, } from '../test_utils'; +const initialNow = 111111; +const dateNowMock = jest.fn(); +dateNowMock.mockReturnValue(initialNow); + +Date.now = dateNowMock; + +const initialState = initialTrustedAppsPageState(); + +const createStateWithDeletionSubmissionResourceState = ( + submissionResourceState: AsyncResourceState +): TrustedAppsListPageState => ({ + ...initialState, + deletionDialog: { ...initialState.deletionDialog, submissionResourceState }, +}); + describe('selectors', () => { describe('needsRefreshOfListData()', () => { it('returns false for outdated resource state and inactive state', () => { - expect( - needsRefreshOfListData({ - listView: createDefaultListView(), - active: false, - createView: undefined, - }) - ).toBe(false); + expect(needsRefreshOfListData(initialState)).toBe(false); }); it('returns true for outdated resource state and active state', () => { - expect( - needsRefreshOfListData({ - listView: createDefaultListView(), - active: true, - createView: undefined, - }) - ).toBe(true); + expect(needsRefreshOfListData({ ...initialState, active: true })).toBe(true); }); it('returns true when current loaded page index is outdated', () => { - const listView = createLoadedListViewWithPagination({ index: 1, size: 20 }); + const listView = createLoadedListViewWithPagination(initialNow, { index: 1, size: 20 }); - expect(needsRefreshOfListData({ listView, active: true, createView: undefined })).toBe(true); + expect(needsRefreshOfListData({ ...initialState, listView, active: true })).toBe(true); }); it('returns true when current loaded page size is outdated', () => { - const listView = createLoadedListViewWithPagination({ index: 0, size: 50 }); + const listView = createLoadedListViewWithPagination(initialNow, { index: 0, size: 50 }); + + expect(needsRefreshOfListData({ ...initialState, listView, active: true })).toBe(true); + }); + + it('returns true when current loaded data timestamp is outdated', () => { + const listView = { + ...createLoadedListViewWithPagination(111111), + freshDataTimestamp: 222222, + }; - expect(needsRefreshOfListData({ listView, active: true, createView: undefined })).toBe(true); + expect(needsRefreshOfListData({ ...initialState, listView, active: true })).toBe(true); }); it('returns false when current loaded data is up to date', () => { - const listView = createLoadedListViewWithPagination(); + const listView = createLoadedListViewWithPagination(initialNow); - expect(needsRefreshOfListData({ listView, active: true, createView: undefined })).toBe(false); + expect(needsRefreshOfListData({ ...initialState, listView, active: true })).toBe(false); }); }); describe('getCurrentListResourceState()', () => { it('returns current list resource state', () => { - const listView = createDefaultListView(); + const state = { ...initialState, listView: createDefaultListView(initialNow) }; - expect( - getCurrentListResourceState({ listView, active: false, createView: undefined }) - ).toStrictEqual(createUninitialisedResourceState()); + expect(getCurrentListResourceState(state)).toStrictEqual(createUninitialisedResourceState()); }); }); describe('getLastLoadedListResourceState()', () => { it('returns last loaded list resource state', () => { - const listView = { - currentListResourceState: createListComplexLoadingResourceState( - createDefaultPaginationInfo(), - 200 - ), - currentPaginationInfo: createDefaultPaginationInfo(), - show: undefined, + const state = { + ...initialState, + listView: { + currentListResourceState: createListComplexLoadingResourceState( + createDefaultPaginationInfo(), + 200, + initialNow + ), + currentPaginationInfo: createDefaultPaginationInfo(), + freshDataTimestamp: initialNow, + show: undefined, + }, }; - expect( - getLastLoadedListResourceState({ listView, active: false, createView: undefined }) - ).toStrictEqual(createListLoadedResourceState(createDefaultPaginationInfo(), 200)); + expect(getLastLoadedListResourceState(state)).toStrictEqual( + createListLoadedResourceState(createDefaultPaginationInfo(), 200, initialNow) + ); }); }); describe('getListItems()', () => { it('returns empty list when no valid data loaded', () => { - expect( - getListItems({ listView: createDefaultListView(), active: false, createView: undefined }) - ).toStrictEqual([]); + const state = { ...initialState, listView: createDefaultListView(initialNow) }; + + expect(getListItems(state)).toStrictEqual([]); }); it('returns last loaded list items', () => { - const listView = { - currentListResourceState: createListComplexLoadingResourceState( - createDefaultPaginationInfo(), - 200 - ), - currentPaginationInfo: createDefaultPaginationInfo(), - show: undefined, + const state = { + ...initialState, + listView: { + currentListResourceState: createListComplexLoadingResourceState( + createDefaultPaginationInfo(), + 200, + initialNow + ), + currentPaginationInfo: createDefaultPaginationInfo(), + freshDataTimestamp: initialNow, + show: undefined, + }, }; - expect(getListItems({ listView, active: false, createView: undefined })).toStrictEqual( + expect(getListItems(state)).toStrictEqual( createSampleTrustedApps(createDefaultPaginationInfo()) ); }); @@ -120,100 +150,239 @@ describe('selectors', () => { describe('getListTotalItemsCount()', () => { it('returns 0 when no valid data loaded', () => { - expect( - getListTotalItemsCount({ - listView: createDefaultListView(), - active: false, - createView: undefined, - }) - ).toBe(0); + const state = { ...initialState, listView: createDefaultListView(initialNow) }; + + expect(getListTotalItemsCount(state)).toBe(0); }); it('returns last loaded total items count', () => { - const listView = { - currentListResourceState: createListComplexLoadingResourceState( - createDefaultPaginationInfo(), - 200 - ), - currentPaginationInfo: createDefaultPaginationInfo(), - show: undefined, + const state = { + ...initialState, + listView: { + currentListResourceState: createListComplexLoadingResourceState( + createDefaultPaginationInfo(), + 200, + initialNow + ), + currentPaginationInfo: createDefaultPaginationInfo(), + freshDataTimestamp: initialNow, + show: undefined, + }, }; - expect(getListTotalItemsCount({ listView, active: false, createView: undefined })).toBe(200); + expect(getListTotalItemsCount(state)).toBe(200); }); }); describe('getListCurrentPageIndex()', () => { it('returns page index', () => { - expect( - getListCurrentPageIndex({ - listView: createDefaultListView(), - active: false, - createView: undefined, - }) - ).toBe(0); + const state = { ...initialState, listView: createDefaultListView(initialNow) }; + + expect(getListCurrentPageIndex(state)).toBe(0); }); }); describe('getListCurrentPageSize()', () => { - it('returns page index', () => { - expect( - getListCurrentPageSize({ - listView: createDefaultListView(), - active: false, - createView: undefined, - }) - ).toBe(20); + it('returns page size', () => { + const state = { ...initialState, listView: createDefaultListView(initialNow) }; + + expect(getListCurrentPageSize(state)).toBe(20); }); }); describe('getListErrorMessage()', () => { it('returns undefined when not in failed state', () => { - const listView = { - currentListResourceState: createListComplexLoadingResourceState( - createDefaultPaginationInfo(), - 200 - ), - currentPaginationInfo: createDefaultPaginationInfo(), - show: undefined, + const state = { + ...initialState, + listView: { + currentListResourceState: createListComplexLoadingResourceState( + createDefaultPaginationInfo(), + 200, + initialNow + ), + currentPaginationInfo: createDefaultPaginationInfo(), + freshDataTimestamp: initialNow, + show: undefined, + }, }; - expect( - getListErrorMessage({ listView, active: false, createView: undefined }) - ).toBeUndefined(); + expect(getListErrorMessage(state)).toBeUndefined(); }); it('returns message when not in failed state', () => { - const listView = { - currentListResourceState: createListFailedResourceState('Internal Server Error'), - currentPaginationInfo: createDefaultPaginationInfo(), - show: undefined, + const state = { + ...initialState, + listView: { + currentListResourceState: createListFailedResourceState('Internal Server Error'), + currentPaginationInfo: createDefaultPaginationInfo(), + freshDataTimestamp: initialNow, + show: undefined, + }, }; - expect(getListErrorMessage({ listView, active: false, createView: undefined })).toBe( - 'Internal Server Error' - ); + expect(getListErrorMessage(state)).toBe('Internal Server Error'); }); }); describe('isListLoading()', () => { it('returns false when no loading is happening', () => { - expect( - isListLoading({ listView: createDefaultListView(), active: false, createView: undefined }) - ).toBe(false); + expect(isListLoading(initialState)).toBe(false); }); it('returns true when loading is in progress', () => { - const listView = { - currentListResourceState: createListComplexLoadingResourceState( - createDefaultPaginationInfo(), - 200 - ), - currentPaginationInfo: createDefaultPaginationInfo(), - show: undefined, + const state = { + ...initialState, + listView: { + currentListResourceState: createListComplexLoadingResourceState( + createDefaultPaginationInfo(), + 200, + initialNow + ), + currentPaginationInfo: createDefaultPaginationInfo(), + freshDataTimestamp: initialNow, + show: undefined, + }, }; - expect(isListLoading({ listView, active: false, createView: undefined })).toBe(true); + expect(isListLoading(state)).toBe(true); + }); + }); + + describe('isDeletionDialogOpen()', () => { + it('returns false when no entry is set', () => { + expect(isDeletionDialogOpen(initialState)).toBe(false); + }); + + it('returns true when entry is set', () => { + const state = { + ...initialState, + deletionDialog: { + ...initialState.deletionDialog, + entry: createSampleTrustedApp(5), + }, + }; + + expect(isDeletionDialogOpen(state)).toBe(true); + }); + }); + + describe('isDeletionInProgress()', () => { + it('returns false when resource state is uninitialised', () => { + expect(isDeletionInProgress(initialState)).toBe(false); + }); + + it('returns true when resource state is loading', () => { + const state = createStateWithDeletionSubmissionResourceState({ + type: 'LoadingResourceState', + previousState: { type: 'UninitialisedResourceState' }, + }); + + expect(isDeletionInProgress(state)).toBe(true); + }); + + it('returns false when resource state is loaded', () => { + const state = createStateWithDeletionSubmissionResourceState({ + type: 'LoadedResourceState', + data: null, + }); + + expect(isDeletionInProgress(state)).toBe(false); + }); + + it('returns false when resource state is failed', () => { + const state = createStateWithDeletionSubmissionResourceState({ + type: 'FailedResourceState', + error: createServerApiError('Not Found'), + }); + + expect(isDeletionInProgress(state)).toBe(false); + }); + }); + + describe('isDeletionSuccessful()', () => { + it('returns false when resource state is uninitialised', () => { + expect(isDeletionSuccessful(initialState)).toBe(false); + }); + + it('returns false when resource state is loading', () => { + const state = createStateWithDeletionSubmissionResourceState({ + type: 'LoadingResourceState', + previousState: { type: 'UninitialisedResourceState' }, + }); + + expect(isDeletionSuccessful(state)).toBe(false); + }); + + it('returns true when resource state is loaded', () => { + const state = createStateWithDeletionSubmissionResourceState({ + type: 'LoadedResourceState', + data: null, + }); + + expect(isDeletionSuccessful(state)).toBe(true); + }); + + it('returns false when resource state is failed', () => { + const state = createStateWithDeletionSubmissionResourceState({ + type: 'FailedResourceState', + error: createServerApiError('Not Found'), + }); + + expect(isDeletionSuccessful(state)).toBe(false); + }); + }); + + describe('getDeletionError()', () => { + it('returns undefined when resource state is uninitialised', () => { + expect(getDeletionError(initialState)).toBeUndefined(); + }); + + it('returns undefined when resource state is loading', () => { + const state = createStateWithDeletionSubmissionResourceState({ + type: 'LoadingResourceState', + previousState: { type: 'UninitialisedResourceState' }, + }); + + expect(getDeletionError(state)).toBeUndefined(); + }); + + it('returns undefined when resource state is loaded', () => { + const state = createStateWithDeletionSubmissionResourceState({ + type: 'LoadedResourceState', + data: null, + }); + + expect(getDeletionError(state)).toBeUndefined(); + }); + + it('returns error when resource state is failed', () => { + const state = createStateWithDeletionSubmissionResourceState({ + type: 'FailedResourceState', + error: createServerApiError('Not Found'), + }); + + expect(getDeletionError(state)).toStrictEqual(createServerApiError('Not Found')); + }); + }); + + describe('getDeletionSubmissionResourceState()', () => { + it('returns submission resource state', () => { + expect(getDeletionSubmissionResourceState(initialState)).toStrictEqual({ + type: 'UninitialisedResourceState', + }); + }); + }); + + describe('getDeletionDialogEntry()', () => { + it('returns undefined when no entry is set', () => { + expect(getDeletionDialogEntry(initialState)).toBeUndefined(); + }); + + it('returns entry when entry is set', () => { + const entry = createSampleTrustedApp(5); + const state = { ...initialState, deletionDialog: { ...initialState.deletionDialog, entry } }; + + expect(getDeletionDialogEntry(state)).toStrictEqual(entry); }); }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts index f074b21f79f4e..6239b425efe2f 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts @@ -5,12 +5,15 @@ */ import { createSelector } from 'reselect'; +import { ServerApiError } from '../../../../common/types'; import { Immutable, NewTrustedApp, TrustedApp } from '../../../../../common/endpoint/types'; import { AsyncResourceState, getCurrentResourceError, getLastLoadedResourceState, + isFailedResourceState, + isLoadedResourceState, isLoadingResourceState, isOutdatedResourceState, LoadedResourceState, @@ -32,12 +35,15 @@ const pageInfosEqual = (pageInfo1: PaginationInfo, pageInfo2: PaginationInfo): b export const needsRefreshOfListData = (state: Immutable): boolean => { const currentPageInfo = state.listView.currentPaginationInfo; const currentPage = state.listView.currentListResourceState; + const freshDataTimestamp = state.listView.freshDataTimestamp; return ( state.active && - isOutdatedResourceState(currentPage, (data) => - pageInfosEqual(currentPageInfo, data.paginationInfo) - ) + isOutdatedResourceState(currentPage, (data) => { + return ( + pageInfosEqual(currentPageInfo, data.paginationInfo) && data.timestamp >= freshDataTimestamp + ); + }) ); }; @@ -104,6 +110,38 @@ export const isListLoading = (state: Immutable): boole return isLoadingResourceState(state.listView.currentListResourceState); }; +export const isDeletionDialogOpen = (state: Immutable): boolean => { + return state.deletionDialog.entry !== undefined; +}; + +export const isDeletionInProgress = (state: Immutable): boolean => { + return isLoadingResourceState(state.deletionDialog.submissionResourceState); +}; + +export const isDeletionSuccessful = (state: Immutable): boolean => { + return isLoadedResourceState(state.deletionDialog.submissionResourceState); +}; + +export const getDeletionError = ( + state: Immutable +): Immutable | undefined => { + const submissionResourceState = state.deletionDialog.submissionResourceState; + + return isFailedResourceState(submissionResourceState) ? submissionResourceState.error : undefined; +}; + +export const getDeletionSubmissionResourceState = ( + state: Immutable +): AsyncResourceState => { + return state.deletionDialog.submissionResourceState; +}; + +export const getDeletionDialogEntry = ( + state: Immutable +): Immutable | undefined => { + return state.deletionDialog.entry; +}; + export const isCreatePending: (state: Immutable) => boolean = ({ createView, }) => { diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/test_utils/index.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/test_utils/index.ts index 70e4e1e685b01..020a87f526e52 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/test_utils/index.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/test_utils/index.ts @@ -4,10 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ +import { combineReducers, createStore } from 'redux'; import { ServerApiError } from '../../../../common/types'; import { TrustedApp } from '../../../../../common/endpoint/types'; import { RoutingAction } from '../../../../common/store/routing'; +import { + MANAGEMENT_STORE_GLOBAL_NAMESPACE, + MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE, +} from '../../../common/constants'; + import { AsyncResourceState, FailedResourceState, @@ -20,30 +26,36 @@ import { UninitialisedResourceState, } from '../state'; +import { trustedAppsPageReducer } from '../store/reducer'; import { TrustedAppsListResourceStateChanged } from '../store/action'; -import { initialTrustedAppsPageState } from '../store/reducer'; const OS_LIST: Array = ['windows', 'macos', 'linux']; -export const createSampleTrustedApps = (paginationInfo: PaginationInfo): TrustedApp[] => { - return [...new Array(paginationInfo.size).keys()].map((i) => ({ - id: String(paginationInfo.index + i), - name: `trusted app ${paginationInfo.index + i}`, - description: `Trusted App ${paginationInfo.index + i}`, +export const createSampleTrustedApp = (i: number): TrustedApp => { + return { + id: String(i), + name: `trusted app ${i}`, + description: `Trusted App ${i}`, created_at: '1 minute ago', created_by: 'someone', os: OS_LIST[i % 3], entries: [], - })); + }; +}; + +export const createSampleTrustedApps = (paginationInfo: PaginationInfo): TrustedApp[] => { + return [...new Array(paginationInfo.size).keys()].map(createSampleTrustedApp); }; export const createTrustedAppsListData = ( paginationInfo: PaginationInfo, - totalItemsCount: number + totalItemsCount: number, + timestamp: number ) => ({ items: createSampleTrustedApps(paginationInfo), totalItemsCount, paginationInfo, + timestamp, }); export const createServerApiError = (message: string) => ({ @@ -58,10 +70,11 @@ export const createUninitialisedResourceState = (): UninitialisedResourceState = export const createListLoadedResourceState = ( paginationInfo: PaginationInfo, - totalItemsCount: number + totalItemsCount: number, + timestamp: number ): LoadedResourceState => ({ type: 'LoadedResourceState', - data: createTrustedAppsListData(paginationInfo, totalItemsCount), + data: createTrustedAppsListData(paginationInfo, totalItemsCount, timestamp), }); export const createListFailedResourceState = ( @@ -82,50 +95,64 @@ export const createListLoadingResourceState = ( export const createListComplexLoadingResourceState = ( paginationInfo: PaginationInfo, - totalItemsCount: number + totalItemsCount: number, + timestamp: number ): LoadingResourceState => createListLoadingResourceState( createListFailedResourceState( 'Internal Server Error', - createListLoadedResourceState(paginationInfo, totalItemsCount) + createListLoadedResourceState(paginationInfo, totalItemsCount, timestamp) ) ); export const createDefaultPaginationInfo = () => ({ index: 0, size: 20 }); -export const createDefaultListView = () => ({ - ...initialTrustedAppsPageState.listView, +export const createDefaultListView = ( + freshDataTimestamp: number +): TrustedAppsListPageState['listView'] => ({ currentListResourceState: createUninitialisedResourceState(), currentPaginationInfo: createDefaultPaginationInfo(), + freshDataTimestamp, + show: undefined, }); export const createLoadingListViewWithPagination = ( + freshDataTimestamp: number, currentPaginationInfo: PaginationInfo, previousState: StaleResourceState = createUninitialisedResourceState() ): TrustedAppsListPageState['listView'] => ({ - ...initialTrustedAppsPageState.listView, currentListResourceState: { type: 'LoadingResourceState', previousState }, currentPaginationInfo, + freshDataTimestamp, + show: undefined, }); export const createLoadedListViewWithPagination = ( + freshDataTimestamp: number, paginationInfo: PaginationInfo = createDefaultPaginationInfo(), currentPaginationInfo: PaginationInfo = createDefaultPaginationInfo(), totalItemsCount: number = 200 ): TrustedAppsListPageState['listView'] => ({ - ...initialTrustedAppsPageState.listView, - currentListResourceState: createListLoadedResourceState(paginationInfo, totalItemsCount), + currentListResourceState: createListLoadedResourceState( + paginationInfo, + totalItemsCount, + freshDataTimestamp + ), currentPaginationInfo, + freshDataTimestamp, + show: undefined, }); export const createFailedListViewWithPagination = ( + freshDataTimestamp: number, currentPaginationInfo: PaginationInfo, error: ServerApiError, lastLoadedState?: LoadedResourceState ): TrustedAppsListPageState['listView'] => ({ - ...initialTrustedAppsPageState.listView, currentListResourceState: { type: 'FailedResourceState', error, lastLoadedState }, currentPaginationInfo, + freshDataTimestamp, + show: undefined, }); export const createUserChangedUrlAction = (path: string, search: string = ''): RoutingAction => { @@ -138,3 +165,13 @@ export const createTrustedAppsListResourceStateChangedAction = ( type: 'trustedAppsListResourceStateChanged', payload: { newState }, }); + +export const createGlobalNoMiddlewareStore = () => { + return createStore( + combineReducers({ + [MANAGEMENT_STORE_GLOBAL_NAMESPACE]: combineReducers({ + [MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE]: trustedAppsPageReducer, + }), + }) + ); +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_app_deletion_dialog.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_app_deletion_dialog.test.tsx.snap new file mode 100644 index 0000000000000..fdb20f229f144 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_app_deletion_dialog.test.tsx.snap @@ -0,0 +1,315 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TrustedAppDeletionDialog renders correctly initially 1`] = ` + +
+ +`; + +exports[`TrustedAppDeletionDialog renders correctly when deletion failed 1`] = ` + +
+
+
+
+ +
+
+
+ Remove trusted application +
+
+
+
+
+

+ You are removing trusted application " + + trusted app 3 + + ". +

+

+ This action cannot be undone. Are you sure you wish to continue? +

+
+
+
+
+ + +
+
+
+
+
+ +`; + +exports[`TrustedAppDeletionDialog renders correctly when deletion is in progress 1`] = ` + +
+
+
+
+ +
+
+
+ Remove trusted application +
+
+
+
+
+

+ You are removing trusted application " + + trusted app 3 + + ". +

+

+ This action cannot be undone. Are you sure you wish to continue? +

+
+
+
+
+ + +
+
+
+
+
+ +`; + +exports[`TrustedAppDeletionDialog renders correctly when dialog started 1`] = ` + +
+
+
+
+ +
+
+
+ Remove trusted application +
+
+
+
+
+

+ You are removing trusted application " + + trusted app 3 + + ". +

+

+ This action cannot be undone. Are you sure you wish to continue? +

+
+
+
+
+ + +
+
+
+
+
+ +`; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_list.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_list.test.tsx.snap index e0f846f5950f7..46885bd653dc2 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_list.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_list.test.tsx.snap @@ -98,6 +98,22 @@ exports[`TrustedAppsList renders correctly initially 1`] = `
+ +
+ + Actions + +
+ @@ -106,7 +122,7 @@ exports[`TrustedAppsList renders correctly initially 1`] = ` >
+ +
+ + Actions + +
+ @@ -232,7 +264,7 @@ exports[`TrustedAppsList renders correctly when failed loading data for the firs >
+ +
+ + Actions + +
+ @@ -363,7 +411,7 @@ exports[`TrustedAppsList renders correctly when failed loading data for the seco >
+ +
+ + Actions + +
+
+ +
+ + + + Delete + + +
+
+ +
+ + + + Delete + + +
+
+ +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ - -
- Name + + + + Delete + + +
+ + + + +
+ Name
+ +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ @@ -2174,6 +2838,22 @@ exports[`TrustedAppsList renders correctly when loading data for the first time + +
+ + Actions + +
+ @@ -2182,7 +2862,7 @@ exports[`TrustedAppsList renders correctly when loading data for the first time >
+ +
+ + Actions + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ - + +
+ + + + Delete + + +
+ + + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ @@ -3890,7 +5186,7 @@ exports[`TrustedAppsList renders correctly when loading data for the second time `; -exports[`TrustedAppsList renders correctly when new page and page sie set (not loading yet) 1`] = ` +exports[`TrustedAppsList renders correctly when new page and page size set (not loading yet) 1`] = `
+ +
+ + Actions + +
+
+ +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ + +
+ + + + Delete + + +
+ diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_page.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_page.test.tsx.snap index c8d9b46d5a0d2..1fccfe012bfba 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_page.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/__snapshots__/trusted_apps_page.test.tsx.snap @@ -129,6 +129,39 @@ Object { + +
+
+ +
+
+
+ +
+ + Actions + +
+ @@ -271,7 +320,7 @@ Object { >
+ +
+
+ +
+
+
+ +
+ + Actions + +
+ @@ -496,7 +594,7 @@ Object { >
( 'xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.path', { defaultMessage: 'Path' } ), - value: 'process.path', + value: 'process.path.text', }, ]; }, []); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.test.tsx new file mode 100644 index 0000000000000..a82d7a9cdba61 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.test.tsx @@ -0,0 +1,126 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { Provider } from 'react-redux'; +import { render } from '@testing-library/react'; +import { I18nProvider } from '@kbn/i18n/react'; + +import { + createGlobalNoMiddlewareStore, + createSampleTrustedApp, + createServerApiError, +} from '../test_utils'; + +import { + TrustedAppDeletionDialogStarted, + TrustedAppDeletionSubmissionResourceStateChanged, +} from '../store/action'; + +import { TrustedAppDeletionDialog } from './trusted_app_deletion_dialog'; + +const renderDeletionDialog = (store: ReturnType) => { + const Wrapper: React.FC = ({ children }) => ( + + {children} + + ); + + return render(, { wrapper: Wrapper }); +}; + +const createDialogStartAction = (): TrustedAppDeletionDialogStarted => ({ + type: 'trustedAppDeletionDialogStarted', + payload: { entry: createSampleTrustedApp(3) }, +}); + +const createDialogLoadingAction = (): TrustedAppDeletionSubmissionResourceStateChanged => ({ + type: 'trustedAppDeletionSubmissionResourceStateChanged', + payload: { + newState: { + type: 'LoadingResourceState', + previousState: { type: 'UninitialisedResourceState' }, + }, + }, +}); + +const createDialogFailedAction = (): TrustedAppDeletionSubmissionResourceStateChanged => ({ + type: 'trustedAppDeletionSubmissionResourceStateChanged', + payload: { + newState: { type: 'FailedResourceState', error: createServerApiError('Not Found') }, + }, +}); + +describe('TrustedAppDeletionDialog', () => { + it('renders correctly initially', () => { + expect(renderDeletionDialog(createGlobalNoMiddlewareStore()).baseElement).toMatchSnapshot(); + }); + + it('renders correctly when dialog started', () => { + const store = createGlobalNoMiddlewareStore(); + + store.dispatch(createDialogStartAction()); + + expect(renderDeletionDialog(store).baseElement).toMatchSnapshot(); + }); + + it('renders correctly when deletion is in progress', () => { + const store = createGlobalNoMiddlewareStore(); + + store.dispatch(createDialogStartAction()); + store.dispatch(createDialogLoadingAction()); + + expect(renderDeletionDialog(store).baseElement).toMatchSnapshot(); + }); + + it('renders correctly when deletion failed', () => { + const store = createGlobalNoMiddlewareStore(); + + store.dispatch(createDialogStartAction()); + store.dispatch(createDialogFailedAction()); + + expect(renderDeletionDialog(store).baseElement).toMatchSnapshot(); + }); + + it('triggers confirmation action when confirm button clicked', async () => { + const store = createGlobalNoMiddlewareStore(); + + store.dispatch(createDialogStartAction()); + store.dispatch = jest.fn(); + + (await renderDeletionDialog(store).findByTestId('trustedAppDeletionConfirm')).click(); + + expect(store.dispatch).toBeCalledWith({ + type: 'trustedAppDeletionDialogConfirmed', + }); + }); + + it('triggers closing action when cancel button clicked', async () => { + const store = createGlobalNoMiddlewareStore(); + + store.dispatch(createDialogStartAction()); + store.dispatch = jest.fn(); + + (await renderDeletionDialog(store).findByTestId('trustedAppDeletionCancel')).click(); + + expect(store.dispatch).toBeCalledWith({ + type: 'trustedAppDeletionDialogClosed', + }); + }); + + it('does not trigger closing action when deletion in progress and cancel button clicked', async () => { + const store = createGlobalNoMiddlewareStore(); + + store.dispatch(createDialogStartAction()); + store.dispatch(createDialogLoadingAction()); + + store.dispatch = jest.fn(); + + (await renderDeletionDialog(store).findByTestId('trustedAppDeletionCancel')).click(); + + expect(store.dispatch).not.toBeCalled(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.tsx new file mode 100644 index 0000000000000..846fa794ceefd --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.tsx @@ -0,0 +1,115 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { memo, useCallback, useMemo } from 'react'; +import { useDispatch } from 'react-redux'; +import { Dispatch } from 'redux'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiButton, + EuiButtonEmpty, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiOverlayMask, + EuiText, +} from '@elastic/eui'; + +import { Immutable, TrustedApp } from '../../../../../common/endpoint/types'; +import { AppAction } from '../../../../common/store/actions'; +import { useTrustedAppsSelector } from './hooks'; +import { + getDeletionDialogEntry, + isDeletionDialogOpen, + isDeletionInProgress, +} from '../store/selectors'; + +const CANCEL_SUBJ = 'trustedAppDeletionCancel'; +const CONFIRM_SUBJ = 'trustedAppDeletionConfirm'; + +const getTranslations = (entry: Immutable | undefined) => ({ + title: ( + + ), + mainMessage: ( + {entry?.name} }} + /> + ), + subMessage: ( + + ), + cancelButton: ( + + ), + confirmButton: ( + + ), +}); + +export const TrustedAppDeletionDialog = memo(() => { + const dispatch = useDispatch>(); + const isBusy = useTrustedAppsSelector(isDeletionInProgress); + const entry = useTrustedAppsSelector(getDeletionDialogEntry); + const translations = useMemo(() => getTranslations(entry), [entry]); + const onConfirm = useCallback(() => { + dispatch({ type: 'trustedAppDeletionDialogConfirmed' }); + }, [dispatch]); + const onCancel = useCallback(() => { + if (!isBusy) { + dispatch({ type: 'trustedAppDeletionDialogClosed' }); + } + }, [dispatch, isBusy]); + + if (useTrustedAppsSelector(isDeletionDialogOpen)) { + return ( + + + + {translations.title} + + + + +

{translations.mainMessage}

+

{translations.subMessage}

+
+
+ + + + {translations.cancelButton} + + + + {translations.confirmButton} + + +
+
+ ); + } else { + return <>; + } +}); + +TrustedAppDeletionDialog.displayName = 'TrustedAppDeletionDialog'; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.test.tsx index 0362f5c7a9de6..a457ecd0d088f 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.test.tsx @@ -3,40 +3,28 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { combineReducers, createStore } from 'redux'; import { render } from '@testing-library/react'; import React from 'react'; import { Provider } from 'react-redux'; -import { - MANAGEMENT_STORE_GLOBAL_NAMESPACE, - MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE, -} from '../../../common/constants'; -import { trustedAppsPageReducer } from '../store/reducer'; import { TrustedAppsList } from './trusted_apps_list'; import { + createSampleTrustedApp, createListFailedResourceState, createListLoadedResourceState, createListLoadingResourceState, createTrustedAppsListResourceStateChangedAction, createUserChangedUrlAction, + createGlobalNoMiddlewareStore, } from '../test_utils'; jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ htmlIdGenerator: () => () => 'mockId', })); -const createStoreSetup = () => { - return createStore( - combineReducers({ - [MANAGEMENT_STORE_GLOBAL_NAMESPACE]: combineReducers({ - [MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE]: trustedAppsPageReducer, - }), - }) - ); -}; +const now = 111111; -const renderList = (store: ReturnType) => { +const renderList = (store: ReturnType) => { const Wrapper: React.FC = ({ children }) => {children}; return render(, { wrapper: Wrapper }); @@ -44,11 +32,11 @@ const renderList = (store: ReturnType) => { describe('TrustedAppsList', () => { it('renders correctly initially', () => { - expect(renderList(createStoreSetup()).container).toMatchSnapshot(); + expect(renderList(createGlobalNoMiddlewareStore()).container).toMatchSnapshot(); }); it('renders correctly when loading data for the first time', () => { - const store = createStoreSetup(); + const store = createGlobalNoMiddlewareStore(); store.dispatch( createTrustedAppsListResourceStateChangedAction(createListLoadingResourceState()) @@ -58,7 +46,7 @@ describe('TrustedAppsList', () => { }); it('renders correctly when failed loading data for the first time', () => { - const store = createStoreSetup(); + const store = createGlobalNoMiddlewareStore(); store.dispatch( createTrustedAppsListResourceStateChangedAction( @@ -70,23 +58,23 @@ describe('TrustedAppsList', () => { }); it('renders correctly when loaded data', () => { - const store = createStoreSetup(); + const store = createGlobalNoMiddlewareStore(); store.dispatch( createTrustedAppsListResourceStateChangedAction( - createListLoadedResourceState({ index: 0, size: 20 }, 200) + createListLoadedResourceState({ index: 0, size: 20 }, 200, now) ) ); expect(renderList(store).container).toMatchSnapshot(); }); - it('renders correctly when new page and page sie set (not loading yet)', () => { - const store = createStoreSetup(); + it('renders correctly when new page and page size set (not loading yet)', () => { + const store = createGlobalNoMiddlewareStore(); store.dispatch( createTrustedAppsListResourceStateChangedAction( - createListLoadedResourceState({ index: 0, size: 20 }, 200) + createListLoadedResourceState({ index: 0, size: 20 }, 200, now) ) ); store.dispatch(createUserChangedUrlAction('/trusted_apps', '?page_index=2&page_size=50')); @@ -95,11 +83,13 @@ describe('TrustedAppsList', () => { }); it('renders correctly when loading data for the second time', () => { - const store = createStoreSetup(); + const store = createGlobalNoMiddlewareStore(); store.dispatch( createTrustedAppsListResourceStateChangedAction( - createListLoadingResourceState(createListLoadedResourceState({ index: 0, size: 20 }, 200)) + createListLoadingResourceState( + createListLoadedResourceState({ index: 0, size: 20 }, 200, now) + ) ) ); @@ -107,17 +97,37 @@ describe('TrustedAppsList', () => { }); it('renders correctly when failed loading data for the second time', () => { - const store = createStoreSetup(); + const store = createGlobalNoMiddlewareStore(); store.dispatch( createTrustedAppsListResourceStateChangedAction( createListFailedResourceState( 'Intenal Server Error', - createListLoadedResourceState({ index: 0, size: 20 }, 200) + createListLoadedResourceState({ index: 0, size: 20 }, 200, now) ) ) ); expect(renderList(store).container).toMatchSnapshot(); }); + + it('triggers deletion dialog when delete action clicked', async () => { + const store = createGlobalNoMiddlewareStore(); + + store.dispatch( + createTrustedAppsListResourceStateChangedAction( + createListLoadedResourceState({ index: 0, size: 20 }, 200, now) + ) + ); + store.dispatch = jest.fn(); + + (await renderList(store).findAllByTestId('trustedAppDeleteAction'))[0].click(); + + expect(store.dispatch).toBeCalledWith({ + type: 'trustedAppDeletionDialogStarted', + payload: { + entry: createSampleTrustedApp(0), + }, + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.tsx index ea834060d5223..c91512d477510 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.tsx @@ -4,12 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Dispatch } from 'redux'; import React, { memo, useCallback, useMemo } from 'react'; +import { useDispatch } from 'react-redux'; import { useHistory } from 'react-router-dom'; -import { EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui'; +import { EuiBasicTable, EuiBasicTableColumn, EuiTableActionsColumnType } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { Immutable } from '../../../../../common/endpoint/types'; +import { AppAction } from '../../../../common/store/actions'; import { TrustedApp } from '../../../../../common/endpoint/types/trusted_apps'; import { MANAGEMENT_PAGE_SIZE_OPTIONS } from '../../../common/constants'; import { getTrustedAppsListPath } from '../../../common/routing'; @@ -28,7 +31,9 @@ import { useTrustedAppsSelector } from './hooks'; import { FormattedDate } from '../../../../common/components/formatted_date'; import { OS_TITLES } from './constants'; -const COLUMN_TITLES: Readonly<{ [K in keyof Omit]: string }> = { +const COLUMN_TITLES: Readonly< + { [K in keyof Omit | 'actions']: string } +> = { name: i18n.translate('xpack.securitySolution.trustedapps.list.columns.name', { defaultMessage: 'Name', }), @@ -41,9 +46,41 @@ const COLUMN_TITLES: Readonly<{ [K in keyof Omit]: created_by: i18n.translate('xpack.securitySolution.trustedapps.list.columns.createdBy', { defaultMessage: 'Created By', }), + actions: i18n.translate('xpack.securitySolution.trustedapps.list.columns.actions', { + defaultMessage: 'Actions', + }), }; -const getColumnDefinitions = (): Array>> => [ +type ActionsList = EuiTableActionsColumnType>['actions']; + +const getActionDefinitions = (dispatch: Dispatch>): ActionsList => [ + { + name: i18n.translate('xpack.securitySolution.trustedapps.list.actions.delete', { + defaultMessage: 'Delete', + }), + description: i18n.translate( + 'xpack.securitySolution.trustedapps.list.actions.delete.description', + { + defaultMessage: 'Delete this entry', + } + ), + 'data-test-subj': 'trustedAppDeleteAction', + isPrimary: true, + icon: 'trash', + color: 'danger', + type: 'icon', + onClick: (item: Immutable) => { + dispatch({ + type: 'trustedAppDeletionDialogStarted', + payload: { entry: item }, + }); + }, + }, +]; + +type ColumnsList = Array>>; + +const getColumnDefinitions = (dispatch: Dispatch>): ColumnsList => [ { field: 'name', name: COLUMN_TITLES.name, @@ -72,6 +109,10 @@ const getColumnDefinitions = (): Array field: 'created_by', name: COLUMN_TITLES.created_by, }, + { + name: COLUMN_TITLES.actions, + actions: getActionDefinitions(dispatch), + }, ]; export const TrustedAppsList = memo(() => { @@ -79,11 +120,12 @@ export const TrustedAppsList = memo(() => { const pageSize = useTrustedAppsSelector(getListCurrentPageSize); const totalItemCount = useTrustedAppsSelector(getListTotalItemsCount); const listItems = useTrustedAppsSelector(getListItems); + const dispatch = useDispatch(); const history = useHistory(); return ( getColumnDefinitions(dispatch), [dispatch])} items={useMemo(() => [...listItems], [listItems])} error={useTrustedAppsSelector(getListErrorMessage)} loading={useTrustedAppsSelector(isListLoading)} diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_notifications.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_notifications.test.tsx new file mode 100644 index 0000000000000..cc45abf493582 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_notifications.test.tsx @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { Provider } from 'react-redux'; +import { render } from '@testing-library/react'; + +import { NotificationsStart } from 'kibana/public'; + +import { coreMock } from '../../../../../../../../src/core/public/mocks'; +import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public/context'; + +import { + createGlobalNoMiddlewareStore, + createSampleTrustedApp, + createServerApiError, +} from '../test_utils'; + +import { TrustedAppsNotifications } from './trusted_apps_notifications'; + +const mockNotifications = () => coreMock.createStart({ basePath: '/mock' }).notifications; + +const renderNotifications = ( + store: ReturnType, + notifications: NotificationsStart +) => { + const Wrapper: React.FC = ({ children }) => ( + + {children} + + ); + + return render(, { wrapper: Wrapper }); +}; + +describe('TrustedAppsNotifications', () => { + it('renders correctly initially', () => { + const notifications = mockNotifications(); + + renderNotifications(createGlobalNoMiddlewareStore(), notifications); + + expect(notifications.toasts.addSuccess).not.toBeCalled(); + expect(notifications.toasts.addDanger).not.toBeCalled(); + }); + + it('shows success notification when deletion successful', () => { + const store = createGlobalNoMiddlewareStore(); + const notifications = mockNotifications(); + + renderNotifications(store, notifications); + + store.dispatch({ + type: 'trustedAppDeletionDialogStarted', + payload: { entry: createSampleTrustedApp(3) }, + }); + store.dispatch({ + type: 'trustedAppDeletionSubmissionResourceStateChanged', + payload: { newState: { type: 'LoadedResourceState', data: null } }, + }); + store.dispatch({ + type: 'trustedAppDeletionDialogClosed', + }); + + expect(notifications.toasts.addSuccess).toBeCalledWith({ + text: '"trusted app 3" has been removed from the Trusted Applications list.', + title: 'Successfully removed', + }); + expect(notifications.toasts.addDanger).not.toBeCalled(); + }); + + it('shows error notification when deletion fails', () => { + const store = createGlobalNoMiddlewareStore(); + const notifications = mockNotifications(); + + renderNotifications(store, notifications); + + store.dispatch({ + type: 'trustedAppDeletionDialogStarted', + payload: { entry: createSampleTrustedApp(3) }, + }); + store.dispatch({ + type: 'trustedAppDeletionSubmissionResourceStateChanged', + payload: { + newState: { type: 'FailedResourceState', error: createServerApiError('Not Found') }, + }, + }); + + expect(notifications.toasts.addSuccess).not.toBeCalled(); + expect(notifications.toasts.addDanger).toBeCalledWith({ + text: + 'Unable to remove "trusted app 3" from the Trusted Applications list. Reason: Not Found', + title: 'Removal failure', + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_notifications.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_notifications.tsx new file mode 100644 index 0000000000000..9c0fe8eb6f0cb --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_notifications.tsx @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { memo } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { ServerApiError } from '../../../../common/types'; +import { Immutable, TrustedApp } from '../../../../../common/endpoint/types'; +import { getDeletionDialogEntry, getDeletionError, isDeletionSuccessful } from '../store/selectors'; + +import { useToasts } from '../../../../common/lib/kibana'; +import { useTrustedAppsSelector } from './hooks'; + +const getDeletionErrorMessage = (error: ServerApiError, entry: Immutable) => { + return { + title: i18n.translate('xpack.securitySolution.trustedapps.deletionError.title', { + defaultMessage: 'Removal failure', + }), + text: i18n.translate('xpack.securitySolution.trustedapps.deletionError.text', { + defaultMessage: + 'Unable to remove "{name}" from the Trusted Applications list. Reason: {message}', + values: { name: entry.name, message: error.message }, + }), + }; +}; + +const getDeletionSuccessMessage = (entry: Immutable) => { + return { + title: i18n.translate('xpack.securitySolution.trustedapps.deletionSuccess.title', { + defaultMessage: 'Successfully removed', + }), + text: i18n.translate('xpack.securitySolution.trustedapps.deletionSuccess.text', { + defaultMessage: '"{name}" has been removed from the Trusted Applications list.', + values: { name: entry?.name }, + }), + }; +}; + +export const TrustedAppsNotifications = memo(() => { + const deletionError = useTrustedAppsSelector(getDeletionError); + const deletionDialogEntry = useTrustedAppsSelector(getDeletionDialogEntry); + const deletionSuccessful = useTrustedAppsSelector(isDeletionSuccessful); + const toasts = useToasts(); + + if (deletionError && deletionDialogEntry) { + toasts.addDanger(getDeletionErrorMessage(deletionError, deletionDialogEntry)); + } + + if (deletionSuccessful && deletionDialogEntry) { + toasts.addSuccess(getDeletionSuccessMessage(deletionDialogEntry)); + } + + return <>; +}); + +TrustedAppsNotifications.displayName = 'TrustedAppsNotifications'; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx index 218cef36ed50a..117d8f25b7039 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx @@ -21,6 +21,15 @@ describe('TrustedAppsPage', () => { let coreStart: AppContextTestRender['coreStart']; let waitForAction: MiddlewareActionSpyHelper['waitForAction']; let render: () => ReturnType; + const originalScrollTo = window.scrollTo; + + beforeAll(() => { + window.scrollTo = () => {}; + }); + + afterAll(() => { + window.scrollTo = originalScrollTo; + }); beforeEach(() => { const mockedContext = createAppRootMockRenderer(); @@ -32,6 +41,7 @@ describe('TrustedAppsPage', () => { reactTestingLibrary.act(() => { history.push('/trusted_apps'); }); + window.scrollTo = jest.fn(); }); test.skip('rendering', () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx index a0dae900eb30e..c1c23a3960962 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx @@ -9,6 +9,8 @@ import { EuiButton } from '@elastic/eui'; import { useHistory } from 'react-router-dom'; import { AdministrationListPage } from '../../../components/administration_list_page'; import { TrustedAppsList } from './trusted_apps_list'; +import { TrustedAppDeletionDialog } from './trusted_app_deletion_dialog'; +import { TrustedAppsNotifications } from './trusted_apps_notifications'; import { CreateTrustedAppFlyout } from './components/create_trusted_app_flyout'; import { getTrustedAppsListPath } from '../../../common/routing'; import { useTrustedAppsSelector } from './hooks'; @@ -63,6 +65,8 @@ export const TrustedAppsPage = memo(() => { } actions={addButton} > + + {showAddFlout && ( = { [MANAGEMENT_STORE_POLICY_LIST_NAMESPACE]: initialPolicyListState(), [MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE]: initialPolicyDetailsState(), [MANAGEMENT_STORE_ENDPOINTS_NAMESPACE]: initialEndpointListState, - [MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE]: initialTrustedAppsPageState, + [MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE]: initialTrustedAppsPageState(), }; /** diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/network/components/kpi_network/__snapshots__/index.test.tsx.snap index 49562162e94a8..c512bd99a7916 100644 --- a/x-pack/plugins/security_solution/public/network/components/kpi_network/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/__snapshots__/index.test.tsx.snap @@ -1,9 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`KpiNetwork Component rendering it renders the default widget 1`] = ` -; data: NetworkKpiStrategyResponse; loading?: boolean; @@ -64,6 +64,6 @@ export const KpiNetworkBaseComponent = React.memo<{ ); }); -KpiNetworkBaseComponent.displayName = 'KpiNetworkBaseComponent'; +NetworkKpiBaseComponent.displayName = 'NetworkKpiBaseComponent'; -export const KpiNetworkBaseComponentManage = manageQuery(KpiNetworkBaseComponent); +export const NetworkKpiBaseComponentManage = manageQuery(NetworkKpiBaseComponent); diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/dns/index.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/dns/index.tsx index 889f3dacc2d98..c3dbbed428c11 100644 --- a/x-pack/plugins/security_solution/public/network/components/kpi_network/dns/index.tsx +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/dns/index.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { StatItems } from '../../../../common/components/stat_items'; import { useNetworkKpiDns } from '../../../containers/kpi_network/dns'; -import { KpiNetworkBaseComponentManage } from '../common'; +import { NetworkKpiBaseComponentManage } from '../common'; import { NetworkKpiProps } from '../types'; import * as i18n from './translations'; @@ -28,6 +28,7 @@ export const fieldsMapping: Readonly = [ const NetworkKpiDnsComponent: React.FC = ({ filterQuery, from, + indexNames, to, narrowDateRange, setQuery, @@ -36,12 +37,13 @@ const NetworkKpiDnsComponent: React.FC = ({ const [loading, { refetch, id, inspect, ...data }] = useNetworkKpiDns({ filterQuery, endDate: to, + indexNames, startDate: from, skip, }); return ( - { +describe('NetworkKpiComponent', () => { const state: State = mockGlobalState; const props = { + filterQuery: '', from: '2019-06-15T06:00:00.000Z', - to: '2019-06-18T06:00:00.000Z', + indexNames: [], narrowDateRange: jest.fn(), - filterQuery: '', setQuery: jest.fn(), skip: true, + to: '2019-06-18T06:00:00.000Z', }; const { storage } = createSecuritySolutionStorageMock(); @@ -53,11 +54,11 @@ describe('KpiNetwork Component', () => { test('it renders the default widget', () => { const wrapper = shallow( - + ); - expect(wrapper.find('KpiNetworkComponent')).toMatchSnapshot(); + expect(wrapper.find('NetworkKpiComponent')).toMatchSnapshot(); }); }); }); diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/index.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/index.tsx index 674e592940fa6..1a04d1cc2c0eb 100644 --- a/x-pack/plugins/security_solution/public/network/components/kpi_network/index.tsx +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/index.tsx @@ -14,8 +14,8 @@ import { NetworkKpiUniqueFlows } from './unique_flows'; import { NetworkKpiUniquePrivateIps } from './unique_private_ips'; import { NetworkKpiProps } from './types'; -export const KpiNetworkComponent = React.memo( - ({ filterQuery, from, to, setQuery, skip, narrowDateRange }) => ( +export const NetworkKpiComponent = React.memo( + ({ filterQuery, from, indexNames, to, setQuery, skip, narrowDateRange }) => ( @@ -23,6 +23,7 @@ export const KpiNetworkComponent = React.memo( ( ( ( ( ( ) ); -KpiNetworkComponent.displayName = 'KpiNetworkComponent'; +NetworkKpiComponent.displayName = 'NetworkKpiComponent'; diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/network_events/index.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/network_events/index.tsx index 3ee2acf1a115c..88ef55e943ea9 100644 --- a/x-pack/plugins/security_solution/public/network/components/kpi_network/network_events/index.tsx +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/network_events/index.tsx @@ -9,7 +9,7 @@ import { euiPaletteColorBlind } from '@elastic/eui'; import { StatItems } from '../../../../common/components/stat_items'; import { useNetworkKpiNetworkEvents } from '../../../containers/kpi_network/network_events'; -import { KpiNetworkBaseComponentManage } from '../common'; +import { NetworkKpiBaseComponentManage } from '../common'; import { NetworkKpiProps } from '../types'; import * as i18n from './translations'; @@ -33,6 +33,7 @@ export const fieldsMapping: Readonly = [ const NetworkKpiNetworkEventsComponent: React.FC = ({ filterQuery, from, + indexNames, to, narrowDateRange, setQuery, @@ -41,12 +42,13 @@ const NetworkKpiNetworkEventsComponent: React.FC = ({ const [loading, { refetch, id, inspect, ...data }] = useNetworkKpiNetworkEvents({ filterQuery, endDate: to, + indexNames, startDate: from, skip, }); return ( - = [ const NetworkKpiTlsHandshakesComponent: React.FC = ({ filterQuery, from, + indexNames, to, narrowDateRange, setQuery, @@ -36,12 +37,13 @@ const NetworkKpiTlsHandshakesComponent: React.FC = ({ const [loading, { refetch, id, inspect, ...data }] = useNetworkKpiTlsHandshakes({ filterQuery, endDate: to, + indexNames, startDate: from, skip, }); return ( - = [ const NetworkKpiUniqueFlowsComponent: React.FC = ({ filterQuery, from, + indexNames, to, narrowDateRange, setQuery, @@ -36,12 +37,13 @@ const NetworkKpiUniqueFlowsComponent: React.FC = ({ const [loading, { refetch, id, inspect, ...data }] = useNetworkKpiUniqueFlows({ filterQuery, endDate: to, + indexNames, startDate: from, skip, }); return ( - = [ const NetworkKpiUniquePrivateIpsComponent: React.FC = ({ filterQuery, from, + indexNames, to, narrowDateRange, setQuery, @@ -55,12 +56,13 @@ const NetworkKpiUniquePrivateIpsComponent: React.FC = ({ const [loading, { refetch, id, inspect, ...data }] = useNetworkKpiUniquePrivateIps({ filterQuery, endDate: to, + indexNames, startDate: from, skip, }); return ( - , - Columns, - Columns, - Columns, - Columns + Columns, + Columns, + Columns, + Columns, + Columns ]; export const getTlsColumns = (tableId: string): TlsColumns => [ diff --git a/x-pack/plugins/security_solution/public/network/components/tls_table/mock.ts b/x-pack/plugins/security_solution/public/network/components/tls_table/mock.ts index a90907eb38854..0e16d76d300de 100644 --- a/x-pack/plugins/security_solution/public/network/components/tls_table/mock.ts +++ b/x-pack/plugins/security_solution/public/network/components/tls_table/mock.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { TlsData } from '../../../graphql/types'; +import { NetworkTlsStrategyResponse } from '../../../../common/search_strategy'; -export const mockTlsData: TlsData = { +export const mockTlsData: NetworkTlsStrategyResponse = { totalCount: 2, edges: [ { @@ -51,4 +51,5 @@ export const mockTlsData: TlsData = { fakeTotalCount: 50, showMorePagesIndicator: true, }, + rawResponse: {} as NetworkTlsStrategyResponse['rawResponse'], }; diff --git a/x-pack/plugins/security_solution/public/network/containers/details/index.tsx b/x-pack/plugins/security_solution/public/network/containers/details/index.tsx index 597f85ff082e2..217241bdadcbb 100644 --- a/x-pack/plugins/security_solution/public/network/containers/details/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/details/index.tsx @@ -9,7 +9,6 @@ import { useState, useEffect, useCallback, useRef } from 'react'; import deepEqual from 'fast-deep-equal'; import { ESTermQuery } from '../../../../common/typed_json'; -import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import { inputsModel } from '../../../common/store'; import { useKibana } from '../../../common/lib/kibana'; import { createFilter } from '../../../common/containers/helpers'; @@ -19,7 +18,11 @@ import { NetworkDetailsRequestOptions, NetworkDetailsStrategyResponse, } from '../../../../common/search_strategy'; -import { AbortError } from '../../../../../../../src/plugins/data/common'; +import { + AbortError, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../src/plugins/data/common'; import * as i18n from './translations'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; @@ -38,6 +41,7 @@ interface UseNetworkDetails { id?: string; docValueFields: DocValueFields[]; ip: string; + indexNames: string[]; filterQuery?: ESTermQuery | string; skip: boolean; } @@ -45,18 +49,18 @@ interface UseNetworkDetails { export const useNetworkDetails = ({ docValueFields, filterQuery, + indexNames, id = ID, skip, ip, }: UseNetworkDetails): [boolean, NetworkDetailsArgs] => { - const { data, notifications, uiSettings } = useKibana().services; + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); const [loading, setLoading] = useState(false); const [networkDetailsRequest, setNetworkDetailsRequest] = useState({ - defaultIndex, + defaultIndex: indexNames, docValueFields: docValueFields ?? [], factoryQueryType: NetworkQueries.details, filterQuery: createFilter(filterQuery), @@ -88,7 +92,7 @@ export const useNetworkDetails = ({ }) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setNetworkDetailsResponse((prevResponse) => ({ @@ -99,7 +103,7 @@ export const useNetworkDetails = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -133,7 +137,7 @@ export const useNetworkDetails = ({ setNetworkDetailsRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, ip, docValueFields: docValueFields ?? [], filterQuery: createFilter(filterQuery), @@ -143,7 +147,7 @@ export const useNetworkDetails = ({ } return prevRequest; }); - }, [defaultIndex, filterQuery, skip, ip, docValueFields]); + }, [indexNames, filterQuery, skip, ip, docValueFields]); useEffect(() => { networkDetailsSearch(networkDetailsRequest); diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx index 295cbff76f6aa..dc60bb0a82ba8 100644 --- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx @@ -8,7 +8,6 @@ import deepEqual from 'fast-deep-equal'; import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; -import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; import { inputsModel } from '../../../../common/store'; import { createFilter } from '../../../../common/containers/helpers'; import { useKibana } from '../../../../common/lib/kibana'; @@ -20,7 +19,11 @@ import { import { ESTermQuery } from '../../../../../common/typed_json'; import * as i18n from './translations'; -import { AbortError } from '../../../../../../../../src/plugins/data/common'; +import { + AbortError, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../../src/plugins/data/common'; import { getInspectResponse } from '../../../../helpers'; import { InspectResponse } from '../../../../types'; @@ -37,6 +40,7 @@ export interface NetworkKpiDnsArgs { interface UseNetworkKpiDns { filterQuery?: ESTermQuery | string; endDate: string; + indexNames: string[]; skip?: boolean; startDate: string; } @@ -44,16 +48,16 @@ interface UseNetworkKpiDns { export const useNetworkKpiDns = ({ filterQuery, endDate, + indexNames, skip = false, startDate, }: UseNetworkKpiDns): [boolean, NetworkKpiDnsArgs] => { - const { data, notifications, uiSettings } = useKibana().services; + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); const [loading, setLoading] = useState(false); const [networkKpiDnsRequest, setNetworkKpiDnsRequest] = useState({ - defaultIndex, + defaultIndex: indexNames, factoryQueryType: NetworkKpiQueries.dns, filterQuery: createFilter(filterQuery), id: ID, @@ -89,7 +93,7 @@ export const useNetworkKpiDns = ({ }) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setNetworkKpiDnsResponse((prevResponse) => ({ @@ -100,7 +104,7 @@ export const useNetworkKpiDns = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -134,7 +138,7 @@ export const useNetworkKpiDns = ({ setNetworkKpiDnsRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, filterQuery: createFilter(filterQuery), timerange: { interval: '12h', @@ -147,7 +151,7 @@ export const useNetworkKpiDns = ({ } return prevRequest; }); - }, [defaultIndex, endDate, filterQuery, skip, startDate]); + }, [indexNames, endDate, filterQuery, skip, startDate]); useEffect(() => { networkKpiDnsSearch(networkKpiDnsRequest); diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx index 8ab94432746f4..a1727d5bb4331 100644 --- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx @@ -8,7 +8,6 @@ import deepEqual from 'fast-deep-equal'; import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; -import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; import { inputsModel } from '../../../../common/store'; import { createFilter } from '../../../../common/containers/helpers'; import { useKibana } from '../../../../common/lib/kibana'; @@ -20,7 +19,11 @@ import { import { ESTermQuery } from '../../../../../common/typed_json'; import * as i18n from './translations'; -import { AbortError } from '../../../../../../../../src/plugins/data/common'; +import { + AbortError, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../../src/plugins/data/common'; import { getInspectResponse } from '../../../../helpers'; import { InspectResponse } from '../../../../types'; @@ -37,6 +40,7 @@ export interface NetworkKpiNetworkEventsArgs { interface UseNetworkKpiNetworkEvents { filterQuery?: ESTermQuery | string; endDate: string; + indexNames: string[]; skip?: boolean; startDate: string; } @@ -44,18 +48,18 @@ interface UseNetworkKpiNetworkEvents { export const useNetworkKpiNetworkEvents = ({ filterQuery, endDate, + indexNames, skip = false, startDate, }: UseNetworkKpiNetworkEvents): [boolean, NetworkKpiNetworkEventsArgs] => { - const { data, notifications, uiSettings } = useKibana().services; + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); const [loading, setLoading] = useState(false); const [networkKpiNetworkEventsRequest, setNetworkKpiNetworkEventsRequest] = useState< NetworkKpiNetworkEventsRequestOptions >({ - defaultIndex, + defaultIndex: indexNames, factoryQueryType: NetworkKpiQueries.networkEvents, filterQuery: createFilter(filterQuery), id: ID, @@ -96,7 +100,7 @@ export const useNetworkKpiNetworkEvents = ({ ) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setNetworkKpiNetworkEventsResponse((prevResponse) => ({ @@ -107,7 +111,7 @@ export const useNetworkKpiNetworkEvents = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -141,7 +145,7 @@ export const useNetworkKpiNetworkEvents = ({ setNetworkKpiNetworkEventsRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, filterQuery: createFilter(filterQuery), timerange: { interval: '12h', @@ -154,7 +158,7 @@ export const useNetworkKpiNetworkEvents = ({ } return prevRequest; }); - }, [defaultIndex, endDate, filterQuery, skip, startDate]); + }, [indexNames, endDate, filterQuery, skip, startDate]); useEffect(() => { networkKpiNetworkEventsSearch(networkKpiNetworkEventsRequest); diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/index.tsx index f7630352fc3c4..bcbe485e82163 100644 --- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/index.tsx @@ -8,7 +8,6 @@ import deepEqual from 'fast-deep-equal'; import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; -import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; import { inputsModel } from '../../../../common/store'; import { createFilter } from '../../../../common/containers/helpers'; import { useKibana } from '../../../../common/lib/kibana'; @@ -20,7 +19,11 @@ import { import { ESTermQuery } from '../../../../../common/typed_json'; import * as i18n from './translations'; -import { AbortError } from '../../../../../../../../src/plugins/data/common'; +import { + AbortError, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../../src/plugins/data/common'; import { getInspectResponse } from '../../../../helpers'; import { InspectResponse } from '../../../../types'; @@ -37,6 +40,7 @@ export interface NetworkKpiTlsHandshakesArgs { interface UseNetworkKpiTlsHandshakes { filterQuery?: ESTermQuery | string; endDate: string; + indexNames: string[]; skip?: boolean; startDate: string; } @@ -44,18 +48,18 @@ interface UseNetworkKpiTlsHandshakes { export const useNetworkKpiTlsHandshakes = ({ filterQuery, endDate, + indexNames, skip = false, startDate, }: UseNetworkKpiTlsHandshakes): [boolean, NetworkKpiTlsHandshakesArgs] => { - const { data, notifications, uiSettings } = useKibana().services; + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); const [loading, setLoading] = useState(false); const [networkKpiTlsHandshakesRequest, setNetworkKpiTlsHandshakesRequest] = useState< NetworkKpiTlsHandshakesRequestOptions >({ - defaultIndex, + defaultIndex: indexNames, factoryQueryType: NetworkKpiQueries.tlsHandshakes, filterQuery: createFilter(filterQuery), id: ID, @@ -96,7 +100,7 @@ export const useNetworkKpiTlsHandshakes = ({ ) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setNetworkKpiTlsHandshakesResponse((prevResponse) => ({ @@ -107,7 +111,7 @@ export const useNetworkKpiTlsHandshakes = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -141,7 +145,7 @@ export const useNetworkKpiTlsHandshakes = ({ setNetworkKpiTlsHandshakesRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, filterQuery: createFilter(filterQuery), timerange: { interval: '12h', @@ -154,7 +158,7 @@ export const useNetworkKpiTlsHandshakes = ({ } return prevRequest; }); - }, [defaultIndex, endDate, filterQuery, skip, startDate]); + }, [indexNames, endDate, filterQuery, skip, startDate]); useEffect(() => { networkKpiTlsHandshakesSearch(networkKpiTlsHandshakesRequest); diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/index.tsx index 5f1bd782b9abd..a4fdefc93fe75 100644 --- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/index.tsx @@ -8,7 +8,6 @@ import deepEqual from 'fast-deep-equal'; import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; -import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; import { inputsModel } from '../../../../common/store'; import { createFilter } from '../../../../common/containers/helpers'; import { useKibana } from '../../../../common/lib/kibana'; @@ -20,7 +19,11 @@ import { import { ESTermQuery } from '../../../../../common/typed_json'; import * as i18n from './translations'; -import { AbortError } from '../../../../../../../../src/plugins/data/common'; +import { + AbortError, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../../src/plugins/data/common'; import { getInspectResponse } from '../../../../helpers'; import { InspectResponse } from '../../../../types'; @@ -37,6 +40,7 @@ export interface NetworkKpiUniqueFlowsArgs { interface UseNetworkKpiUniqueFlows { filterQuery?: ESTermQuery | string; endDate: string; + indexNames: string[]; skip?: boolean; startDate: string; } @@ -44,18 +48,18 @@ interface UseNetworkKpiUniqueFlows { export const useNetworkKpiUniqueFlows = ({ filterQuery, endDate, + indexNames, skip = false, startDate, }: UseNetworkKpiUniqueFlows): [boolean, NetworkKpiUniqueFlowsArgs] => { - const { data, notifications, uiSettings } = useKibana().services; + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); const [loading, setLoading] = useState(false); const [networkKpiUniqueFlowsRequest, setNetworkKpiUniqueFlowsRequest] = useState< NetworkKpiUniqueFlowsRequestOptions >({ - defaultIndex, + defaultIndex: indexNames, factoryQueryType: NetworkKpiQueries.uniqueFlows, filterQuery: createFilter(filterQuery), id: ID, @@ -96,7 +100,7 @@ export const useNetworkKpiUniqueFlows = ({ ) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setNetworkKpiUniqueFlowsResponse((prevResponse) => ({ @@ -107,7 +111,7 @@ export const useNetworkKpiUniqueFlows = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -141,7 +145,7 @@ export const useNetworkKpiUniqueFlows = ({ setNetworkKpiUniqueFlowsRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, filterQuery: createFilter(filterQuery), timerange: { interval: '12h', @@ -154,7 +158,7 @@ export const useNetworkKpiUniqueFlows = ({ } return prevRequest; }); - }, [defaultIndex, endDate, filterQuery, skip, startDate]); + }, [indexNames, endDate, filterQuery, skip, startDate]); useEffect(() => { networkKpiUniqueFlowsSearch(networkKpiUniqueFlowsRequest); diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/index.tsx index f32f43d811137..5e9d829077f23 100644 --- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/index.tsx @@ -8,7 +8,6 @@ import deepEqual from 'fast-deep-equal'; import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; -import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; import { inputsModel } from '../../../../common/store'; import { createFilter } from '../../../../common/containers/helpers'; import { useKibana } from '../../../../common/lib/kibana'; @@ -21,7 +20,11 @@ import { import { ESTermQuery } from '../../../../../common/typed_json'; import * as i18n from './translations'; -import { AbortError } from '../../../../../../../../src/plugins/data/common'; +import { + AbortError, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../../src/plugins/data/common'; import { getInspectResponse } from '../../../../helpers'; import { InspectResponse } from '../../../../types'; @@ -41,6 +44,7 @@ export interface NetworkKpiUniquePrivateIpsArgs { interface UseNetworkKpiUniquePrivateIps { filterQuery?: ESTermQuery | string; endDate: string; + indexNames: string[]; skip?: boolean; startDate: string; } @@ -48,18 +52,18 @@ interface UseNetworkKpiUniquePrivateIps { export const useNetworkKpiUniquePrivateIps = ({ filterQuery, endDate, + indexNames, skip = false, startDate, }: UseNetworkKpiUniquePrivateIps): [boolean, NetworkKpiUniquePrivateIpsArgs] => { - const { data, notifications, uiSettings } = useKibana().services; + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); const [loading, setLoading] = useState(false); const [networkKpiUniquePrivateIpsRequest, setNetworkKpiUniquePrivateIpsRequest] = useState< NetworkKpiUniquePrivateIpsRequestOptions >({ - defaultIndex, + defaultIndex: indexNames, factoryQueryType: NetworkKpiQueries.uniquePrivateIps, filterQuery: createFilter(filterQuery), id: ID, @@ -103,7 +107,7 @@ export const useNetworkKpiUniquePrivateIps = ({ }) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setNetworkKpiUniquePrivateIpsResponse((prevResponse) => ({ @@ -118,7 +122,7 @@ export const useNetworkKpiUniquePrivateIps = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -152,7 +156,7 @@ export const useNetworkKpiUniquePrivateIps = ({ setNetworkKpiUniquePrivateIpsRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, filterQuery: createFilter(filterQuery), timerange: { interval: '12h', @@ -165,7 +169,7 @@ export const useNetworkKpiUniquePrivateIps = ({ } return prevRequest; }); - }, [defaultIndex, endDate, filterQuery, skip, startDate]); + }, [indexNames, endDate, filterQuery, skip, startDate]); useEffect(() => { networkKpiUniquePrivateIpsSearch(networkKpiUniquePrivateIpsRequest); diff --git a/x-pack/plugins/security_solution/public/network/containers/network_dns/index.tsx b/x-pack/plugins/security_solution/public/network/containers/network_dns/index.tsx index 6f48cba2ebda1..334373c4a551a 100644 --- a/x-pack/plugins/security_solution/public/network/containers/network_dns/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/network_dns/index.tsx @@ -10,7 +10,6 @@ import { shallowEqual, useSelector } from 'react-redux'; import deepEqual from 'fast-deep-equal'; import { ESTermQuery } from '../../../../common/typed_json'; -import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import { inputsModel, State } from '../../../common/store'; import { useKibana } from '../../../common/lib/kibana'; import { createFilter } from '../../../common/containers/helpers'; @@ -23,7 +22,11 @@ import { NetworkDnsStrategyResponse, MatrixOverOrdinalHistogramData, } from '../../../../common/search_strategy/security_solution/network'; -import { AbortError } from '../../../../../../../src/plugins/data/common'; +import { + AbortError, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../src/plugins/data/common'; import * as i18n from './translations'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; @@ -47,6 +50,7 @@ export interface NetworkDnsArgs { interface UseNetworkDns { id?: string; + indexNames: string[]; type: networkModel.NetworkType; filterQuery?: ESTermQuery | string; endDate: string; @@ -58,6 +62,7 @@ export const useNetworkDns = ({ endDate, filterQuery, id = ID, + indexNames, skip, startDate, type, @@ -67,14 +72,13 @@ export const useNetworkDns = ({ (state: State) => getNetworkDnsSelector(state), shallowEqual ); - const { data, notifications, uiSettings } = useKibana().services; + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); const [loading, setLoading] = useState(false); const [networkDnsRequest, setNetworkDnsRequest] = useState({ - defaultIndex, + defaultIndex: indexNames, factoryQueryType: NetworkQueries.dns, filterQuery: createFilter(filterQuery), isPtrIncluded, @@ -130,7 +134,7 @@ export const useNetworkDns = ({ }) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setNetworkDnsResponse((prevResponse) => ({ @@ -144,7 +148,7 @@ export const useNetworkDns = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -178,7 +182,7 @@ export const useNetworkDns = ({ setNetworkDnsRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, isPtrIncluded, filterQuery: createFilter(filterQuery), pagination: generateTablePaginationOptions(activePage, limit), @@ -194,7 +198,7 @@ export const useNetworkDns = ({ } return prevRequest; }); - }, [activePage, defaultIndex, endDate, filterQuery, limit, startDate, sort, skip, isPtrIncluded]); + }, [activePage, indexNames, endDate, filterQuery, limit, startDate, sort, skip, isPtrIncluded]); useEffect(() => { networkDnsSearch(networkDnsRequest); diff --git a/x-pack/plugins/security_solution/public/network/containers/network_http/index.tsx b/x-pack/plugins/security_solution/public/network/containers/network_http/index.tsx index d3e8067d1802e..221b693818c50 100644 --- a/x-pack/plugins/security_solution/public/network/containers/network_http/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/network_http/index.tsx @@ -10,7 +10,6 @@ import { shallowEqual, useSelector } from 'react-redux'; import deepEqual from 'fast-deep-equal'; import { ESTermQuery } from '../../../../common/typed_json'; -import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import { inputsModel, State } from '../../../common/store'; import { useKibana } from '../../../common/lib/kibana'; import { createFilter } from '../../../common/containers/helpers'; @@ -23,7 +22,11 @@ import { NetworkHttpStrategyResponse, SortField, } from '../../../../common/search_strategy'; -import { AbortError } from '../../../../../../../src/plugins/data/common'; +import { + AbortError, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../src/plugins/data/common'; import * as i18n from './translations'; import { InspectResponse } from '../../../types'; import { getInspectResponse } from '../../../helpers'; @@ -45,6 +48,7 @@ export interface NetworkHttpArgs { interface UseNetworkHttp { id?: string; ip?: string; + indexNames: string[]; type: networkModel.NetworkType; filterQuery?: ESTermQuery | string; endDate: string; @@ -56,6 +60,7 @@ export const useNetworkHttp = ({ endDate, filterQuery, id = ID, + indexNames, ip, skip, startDate, @@ -66,14 +71,13 @@ export const useNetworkHttp = ({ (state: State) => getHttpSelector(state, type), shallowEqual ); - const { data, notifications, uiSettings } = useKibana().services; + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); const [loading, setLoading] = useState(false); const [networkHttpRequest, setHostRequest] = useState({ - defaultIndex, + defaultIndex: indexNames, factoryQueryType: NetworkQueries.http, filterQuery: createFilter(filterQuery), ip, @@ -130,7 +134,7 @@ export const useNetworkHttp = ({ }) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setNetworkHttpResponse((prevResponse) => ({ @@ -143,7 +147,7 @@ export const useNetworkHttp = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -177,7 +181,7 @@ export const useNetworkHttp = ({ setHostRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, filterQuery: createFilter(filterQuery), pagination: generateTablePaginationOptions(activePage, limit), sort: sort as SortField, @@ -192,7 +196,7 @@ export const useNetworkHttp = ({ } return prevRequest; }); - }, [activePage, defaultIndex, endDate, filterQuery, limit, startDate, sort, skip]); + }, [activePage, indexNames, endDate, filterQuery, limit, startDate, sort, skip]); useEffect(() => { networkHttpSearch(networkHttpRequest); diff --git a/x-pack/plugins/security_solution/public/network/containers/network_top_countries/index.tsx b/x-pack/plugins/security_solution/public/network/containers/network_top_countries/index.tsx index 747f5e4f502dd..6b52966342e97 100644 --- a/x-pack/plugins/security_solution/public/network/containers/network_top_countries/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/network_top_countries/index.tsx @@ -10,7 +10,6 @@ import { shallowEqual, useSelector } from 'react-redux'; import deepEqual from 'fast-deep-equal'; import { ESTermQuery } from '../../../../common/typed_json'; -import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import { inputsModel, State } from '../../../common/store'; import { useKibana } from '../../../common/lib/kibana'; import { createFilter } from '../../../common/containers/helpers'; @@ -24,7 +23,11 @@ import { NetworkTopCountriesStrategyResponse, PageInfoPaginated, } from '../../../../common/search_strategy'; -import { AbortError } from '../../../../../../../src/plugins/data/common'; +import { + AbortError, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../src/plugins/data/common'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; import * as i18n from './translations'; @@ -45,6 +48,7 @@ export interface NetworkTopCountriesArgs { interface UseNetworkTopCountries { flowTarget: FlowTargetSourceDest; ip?: string; + indexNames: string[]; type: networkModel.NetworkType; filterQuery?: ESTermQuery | string; endDate: string; @@ -56,6 +60,7 @@ export const useNetworkTopCountries = ({ endDate, filterQuery, flowTarget, + indexNames, skip, startDate, type, @@ -65,14 +70,13 @@ export const useNetworkTopCountries = ({ (state: State) => getTopCountriesSelector(state, type, flowTarget), shallowEqual ); - const { data, notifications, uiSettings } = useKibana().services; + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); const [loading, setLoading] = useState(false); const [networkTopCountriesRequest, setHostRequest] = useState({ - defaultIndex, + defaultIndex: indexNames, factoryQueryType: NetworkQueries.topCountries, filterQuery: createFilter(filterQuery), flowTarget, @@ -129,7 +133,7 @@ export const useNetworkTopCountries = ({ }) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setNetworkTopCountriesResponse((prevResponse) => ({ @@ -142,7 +146,7 @@ export const useNetworkTopCountries = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -176,7 +180,7 @@ export const useNetworkTopCountries = ({ setHostRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, filterQuery: createFilter(filterQuery), pagination: generateTablePaginationOptions(activePage, limit), sort, @@ -191,7 +195,7 @@ export const useNetworkTopCountries = ({ } return prevRequest; }); - }, [activePage, defaultIndex, endDate, filterQuery, limit, startDate, sort, skip]); + }, [activePage, indexNames, endDate, filterQuery, limit, startDate, sort, skip]); useEffect(() => { networkTopCountriesSearch(networkTopCountriesRequest); diff --git a/x-pack/plugins/security_solution/public/network/containers/network_top_n_flow/index.tsx b/x-pack/plugins/security_solution/public/network/containers/network_top_n_flow/index.tsx index cc0da816c57ec..d6dd14b3259f0 100644 --- a/x-pack/plugins/security_solution/public/network/containers/network_top_n_flow/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/network_top_n_flow/index.tsx @@ -10,7 +10,6 @@ import { shallowEqual, useSelector } from 'react-redux'; import deepEqual from 'fast-deep-equal'; import { ESTermQuery } from '../../../../common/typed_json'; -import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import { inputsModel, State } from '../../../common/store'; import { useKibana } from '../../../common/lib/kibana'; import { createFilter } from '../../../common/containers/helpers'; @@ -24,7 +23,11 @@ import { NetworkTopNFlowStrategyResponse, PageInfoPaginated, } from '../../../../common/search_strategy'; -import { AbortError } from '../../../../../../../src/plugins/data/common'; +import { + AbortError, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../src/plugins/data/common'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; import * as i18n from './translations'; @@ -45,6 +48,7 @@ export interface NetworkTopNFlowArgs { interface UseNetworkTopNFlow { flowTarget: FlowTargetSourceDest; ip?: string; + indexNames: string[]; type: networkModel.NetworkType; filterQuery?: ESTermQuery | string; endDate: string; @@ -56,6 +60,7 @@ export const useNetworkTopNFlow = ({ endDate, filterQuery, flowTarget, + indexNames, skip, startDate, type, @@ -65,14 +70,13 @@ export const useNetworkTopNFlow = ({ (state: State) => getTopNFlowSelector(state, type, flowTarget), shallowEqual ); - const { data, notifications, uiSettings } = useKibana().services; + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); const [loading, setLoading] = useState(false); const [networkTopNFlowRequest, setTopNFlowRequest] = useState({ - defaultIndex, + defaultIndex: indexNames, factoryQueryType: NetworkQueries.topNFlow, filterQuery: createFilter(filterQuery), flowTarget, @@ -127,7 +131,7 @@ export const useNetworkTopNFlow = ({ }) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setNetworkTopNFlowResponse((prevResponse) => ({ @@ -140,7 +144,7 @@ export const useNetworkTopNFlow = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -174,7 +178,7 @@ export const useNetworkTopNFlow = ({ setTopNFlowRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, filterQuery: createFilter(filterQuery), pagination: generateTablePaginationOptions(activePage, limit), timerange: { @@ -189,7 +193,7 @@ export const useNetworkTopNFlow = ({ } return prevRequest; }); - }, [activePage, defaultIndex, endDate, filterQuery, limit, startDate, sort, skip]); + }, [activePage, indexNames, endDate, filterQuery, limit, startDate, sort, skip]); useEffect(() => { networkTopNFlowSearch(networkTopNFlowRequest); diff --git a/x-pack/plugins/security_solution/public/network/containers/tls/index.gql_query.ts b/x-pack/plugins/security_solution/public/network/containers/tls/index.gql_query.ts deleted file mode 100644 index f513a94d69667..0000000000000 --- a/x-pack/plugins/security_solution/public/network/containers/tls/index.gql_query.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import gql from 'graphql-tag'; - -export const tlsQuery = gql` - query GetTlsQuery( - $sourceId: ID! - $filterQuery: String - $flowTarget: FlowTargetSourceDest! - $ip: String! - $pagination: PaginationInputPaginated! - $sort: TlsSortField! - $timerange: TimerangeInput! - $defaultIndex: [String!]! - $inspect: Boolean! - ) { - source(id: $sourceId) { - id - Tls( - filterQuery: $filterQuery - flowTarget: $flowTarget - ip: $ip - pagination: $pagination - sort: $sort - timerange: $timerange - defaultIndex: $defaultIndex - ) { - totalCount - edges { - node { - _id - subjects - ja3 - issuers - notAfter - } - cursor { - value - } - } - pageInfo { - activePage - fakeTotalCount - showMorePagesIndicator - } - inspect @include(if: $inspect) { - dsl - response - } - } - } - } -`; diff --git a/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx b/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx index df02acf208603..f40675a1255ff 100644 --- a/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx @@ -10,11 +10,10 @@ import { shallowEqual, useSelector } from 'react-redux'; import deepEqual from 'fast-deep-equal'; import { ESTermQuery } from '../../../../common/typed_json'; -import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import { inputsModel, State } from '../../../common/store'; import { useKibana } from '../../../common/lib/kibana'; import { createFilter } from '../../../common/containers/helpers'; -import { TlsEdges, PageInfoPaginated, FlowTargetSourceDest } from '../../../graphql/types'; +import { PageInfoPaginated, FlowTargetSourceDest } from '../../../graphql/types'; import { generateTablePaginationOptions } from '../../../common/components/paginated_table/helpers'; import { networkModel, networkSelectors } from '../../store'; import { @@ -22,7 +21,11 @@ import { NetworkTlsRequestOptions, NetworkTlsStrategyResponse, } from '../../../../common/search_strategy/security_solution/network'; -import { AbortError } from '../../../../../../../src/plugins/data/common'; +import { + AbortError, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../src/plugins/data/common'; import * as i18n from './translations'; import { getInspectResponse } from '../../../helpers'; @@ -36,12 +39,13 @@ export interface NetworkTlsArgs { loadPage: (newActivePage: number) => void; pageInfo: PageInfoPaginated; refetch: inputsModel.Refetch; - tls: TlsEdges[]; + tls: NetworkTlsStrategyResponse['edges']; totalCount: number; } interface UseNetworkTls { flowTarget: FlowTargetSourceDest; + indexNames: string[]; ip: string; type: networkModel.NetworkType; filterQuery?: ESTermQuery | string; @@ -56,6 +60,7 @@ export const useNetworkTls = ({ filterQuery, flowTarget, id = ID, + indexNames, ip, skip, startDate, @@ -66,17 +71,17 @@ export const useNetworkTls = ({ (state: State) => getTlsSelector(state, type, flowTarget), shallowEqual ); - const { data, notifications, uiSettings } = useKibana().services; + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); const [loading, setLoading] = useState(false); const [networkTlsRequest, setHostRequest] = useState({ - defaultIndex, + defaultIndex: indexNames, factoryQueryType: NetworkQueries.tls, filterQuery: createFilter(filterQuery), flowTarget, + id, ip, pagination: generateTablePaginationOptions(activePage, limit), sort, @@ -129,7 +134,7 @@ export const useNetworkTls = ({ }) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setNetworkTlsResponse((prevResponse) => ({ @@ -142,7 +147,7 @@ export const useNetworkTls = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -173,7 +178,7 @@ export const useNetworkTls = ({ setHostRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, filterQuery: createFilter(filterQuery), pagination: generateTablePaginationOptions(activePage, limit), timerange: { @@ -188,7 +193,7 @@ export const useNetworkTls = ({ } return prevRequest; }); - }, [activePage, defaultIndex, endDate, filterQuery, limit, startDate, sort, skip]); + }, [activePage, indexNames, endDate, filterQuery, limit, startDate, sort, skip]); useEffect(() => { networkTlsSearch(networkTlsRequest); diff --git a/x-pack/plugins/security_solution/public/network/containers/users/index.tsx b/x-pack/plugins/security_solution/public/network/containers/users/index.tsx index 608ccdb084709..a289f8d16e9b2 100644 --- a/x-pack/plugins/security_solution/public/network/containers/users/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/users/index.tsx @@ -23,7 +23,11 @@ import { NetworkUsersRequestOptions, NetworkUsersStrategyResponse, } from '../../../../common/search_strategy/security_solution/network'; -import { AbortError } from '../../../../../../../src/plugins/data/common'; +import { + AbortError, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../src/plugins/data/common'; import * as i18n from './translations'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; @@ -126,7 +130,7 @@ export const useNetworkUsers = ({ }) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setNetworkUsersResponse((prevResponse) => ({ @@ -139,7 +143,7 @@ export const useNetworkUsers = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } diff --git a/x-pack/plugins/security_solution/public/network/pages/details/index.test.tsx b/x-pack/plugins/security_solution/public/network/pages/details/index.test.tsx index beae3e4f72aaa..430b5702be1bc 100644 --- a/x-pack/plugins/security_solution/public/network/pages/details/index.test.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/details/index.test.tsx @@ -9,7 +9,7 @@ import { Router, useParams } from 'react-router-dom'; import '../../../common/mock/match_media'; -import { useWithSource } from '../../../common/containers/source'; +import { useSourcererScope } from '../../../common/containers/sourcerer'; import { FlowTarget } from '../../../graphql/types'; import { apolloClientObservable, @@ -40,7 +40,7 @@ jest.mock('../../containers/details', () => ({ useNetworkDetails: jest.fn().mockReturnValue([true, { networkDetails: {} }]), })); jest.mock('../../../common/lib/kibana'); -jest.mock('../../../common/containers/source'); +jest.mock('../../../common/containers/sourcerer'); jest.mock('../../../common/containers/use_global_time', () => ({ useGlobalTime: jest.fn().mockReturnValue({ from: '2020-07-07T08:20:18.966Z', @@ -81,7 +81,7 @@ const getMockHistory = (ip: string) => ({ describe('Network Details', () => { const mount = useMountAppended(); beforeAll(() => { - (useWithSource as jest.Mock).mockReturnValue({ + (useSourcererScope as jest.Mock).mockReturnValue({ indicesExist: false, indexPattern: {}, }); @@ -137,7 +137,7 @@ describe('Network Details', () => { test('it renders ipv6 headline', async () => { const ip = 'fe80--24ce-f7ff-fede-a571'; - (useWithSource as jest.Mock).mockReturnValue({ + (useSourcererScope as jest.Mock).mockReturnValue({ indicesExist: true, indexPattern: {}, }); diff --git a/x-pack/plugins/security_solution/public/network/pages/details/index.tsx b/x-pack/plugins/security_solution/public/network/pages/details/index.tsx index 085cddf53ff65..eaeb31c020473 100644 --- a/x-pack/plugins/security_solution/public/network/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/details/index.tsx @@ -24,7 +24,6 @@ import { IpOverview } from '../../components/details'; import { SiemSearchBar } from '../../../common/components/search_bar'; import { WrapperPage } from '../../../common/components/wrapper_page'; import { useNetworkDetails } from '../../containers/details'; -import { useWithSource } from '../../../common/containers/source'; import { FlowTargetSourceDest, LastEventIndexKey } from '../../../graphql/types'; import { useKibana } from '../../../common/lib/kibana'; import { decodeIpv6 } from '../../../common/lib/helpers'; @@ -44,6 +43,7 @@ import { AnomaliesQueryTabBody } from '../../../common/containers/anomalies/anom import { esQuery } from '../../../../../../../src/plugins/data/public'; import { networkModel } from '../../store'; import { SecurityPageName } from '../../../app/types'; +import { useSourcererScope } from '../../../common/containers/sourcerer'; export { getBreadcrumbs } from './utils'; const NetworkDetailsManage = manageQuery(IpOverview); @@ -83,7 +83,7 @@ const NetworkDetailsComponent: React.FC = () => { dispatch(setNetworkDetailsTablesActivePageToZero()); }, [detailName, dispatch]); - const { docValueFields, indicesExist, indexPattern } = useWithSource(); + const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope(); const ip = decodeIpv6(detailName); const filterQuery = convertToBuildEsQuery({ config: esQuery.getEsQueryConfig(uiSettings), @@ -96,6 +96,7 @@ const NetworkDetailsComponent: React.FC = () => { docValueFields, skip: isInitializing, filterQuery, + indexNames: selectedPatterns, ip, }); @@ -124,7 +125,14 @@ const NetworkDetailsComponent: React.FC = () => { border data-test-subj="network-details-headline" draggableArguments={headerDraggableArguments} - subtitle={} + subtitle={ + + } title={ip} > @@ -155,6 +163,7 @@ const NetworkDetailsComponent: React.FC = () => { endDate={to} filterQuery={filterQuery} flowTarget={FlowTargetSourceDest.source} + indexNames={selectedPatterns} ip={ip} skip={isInitializing} startDate={from} @@ -169,6 +178,7 @@ const NetworkDetailsComponent: React.FC = () => { endDate={to} flowTarget={FlowTargetSourceDest.destination} filterQuery={filterQuery} + indexNames={selectedPatterns} ip={ip} skip={isInitializing} startDate={from} @@ -187,6 +197,7 @@ const NetworkDetailsComponent: React.FC = () => { endDate={to} filterQuery={filterQuery} flowTarget={FlowTargetSourceDest.source} + indexNames={selectedPatterns} ip={ip} skip={isInitializing} startDate={from} @@ -201,6 +212,7 @@ const NetworkDetailsComponent: React.FC = () => { endDate={to} flowTarget={FlowTargetSourceDest.destination} filterQuery={filterQuery} + indexNames={selectedPatterns} ip={ip} skip={isInitializing} startDate={from} @@ -217,6 +229,7 @@ const NetworkDetailsComponent: React.FC = () => { endDate={to} filterQuery={filterQuery} flowTarget={flowTarget} + indexNames={selectedPatterns} ip={ip} skip={isInitializing} startDate={from} @@ -229,6 +242,7 @@ const NetworkDetailsComponent: React.FC = () => { { endDate={to} filterQuery={filterQuery} flowTarget={(flowTarget as unknown) as FlowTargetSourceDest} + indexNames={selectedPatterns} ip={ip} setQuery={setQuery} skip={isInitializing} @@ -257,6 +272,7 @@ const NetworkDetailsComponent: React.FC = () => { startDate={from} endDate={to} skip={isInitializing} + indexNames={selectedPatterns} ip={ip} type={type} flowTarget={flowTarget} diff --git a/x-pack/plugins/security_solution/public/network/pages/details/network_http_query_table.tsx b/x-pack/plugins/security_solution/public/network/pages/details/network_http_query_table.tsx index 1b1b2b5f4f46e..0a88519390486 100644 --- a/x-pack/plugins/security_solution/public/network/pages/details/network_http_query_table.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/details/network_http_query_table.tsx @@ -28,6 +28,7 @@ export const NetworkHttpQueryTable = ({ ] = useNetworkHttp({ endDate, filterQuery, + indexNames: [], ip, skip, startDate, diff --git a/x-pack/plugins/security_solution/public/network/pages/details/network_top_countries_query_table.tsx b/x-pack/plugins/security_solution/public/network/pages/details/network_top_countries_query_table.tsx index 42ddd3a6bb4a4..8a7d499a8ef5f 100644 --- a/x-pack/plugins/security_solution/public/network/pages/details/network_top_countries_query_table.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/details/network_top_countries_query_table.tsx @@ -31,6 +31,7 @@ export const NetworkTopCountriesQueryTable = ({ endDate, flowTarget, filterQuery, + indexNames: [], ip, skip, startDate, diff --git a/x-pack/plugins/security_solution/public/network/pages/details/network_top_n_flow_query_table.tsx b/x-pack/plugins/security_solution/public/network/pages/details/network_top_n_flow_query_table.tsx index 821452201b78b..d56d6d4f6b3ee 100644 --- a/x-pack/plugins/security_solution/public/network/pages/details/network_top_n_flow_query_table.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/details/network_top_n_flow_query_table.tsx @@ -18,6 +18,7 @@ export const NetworkTopNFlowQueryTable = ({ filterQuery, flowTarget, ip, + indexNames, setQuery, skip, startDate, @@ -30,6 +31,7 @@ export const NetworkTopNFlowQueryTable = ({ endDate, filterQuery, flowTarget, + indexNames, ip, skip, startDate, diff --git a/x-pack/plugins/security_solution/public/network/pages/details/tls_query_table.tsx b/x-pack/plugins/security_solution/public/network/pages/details/tls_query_table.tsx index 5184fccecf07a..b8c53cdf10fee 100644 --- a/x-pack/plugins/security_solution/public/network/pages/details/tls_query_table.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/details/tls_query_table.tsx @@ -30,6 +30,7 @@ export const TlsQueryTable = ({ endDate, filterQuery, flowTarget, + indexNames: [], ip, skip, startDate, diff --git a/x-pack/plugins/security_solution/public/network/pages/details/types.ts b/x-pack/plugins/security_solution/public/network/pages/details/types.ts index 960df0d5e36b9..3b5a7fab3c6e7 100644 --- a/x-pack/plugins/security_solution/public/network/pages/details/types.ts +++ b/x-pack/plugins/security_solution/public/network/pages/details/types.ts @@ -22,6 +22,7 @@ export interface OwnProps { endDate: string; filterQuery: string | ESTermQuery; ip: string; + indexNames: string[]; skip: boolean; setQuery: GlobalTimeArgs['setQuery']; } diff --git a/x-pack/plugins/security_solution/public/network/pages/navigation/countries_query_tab_body.tsx b/x-pack/plugins/security_solution/public/network/pages/navigation/countries_query_tab_body.tsx index 1e57ca42257e7..1c61760d9845f 100644 --- a/x-pack/plugins/security_solution/public/network/pages/navigation/countries_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/navigation/countries_query_tab_body.tsx @@ -19,6 +19,7 @@ const NetworkTopCountriesTableManage = manageQuery(NetworkTopCountriesTable); export const CountriesQueryTabBody = ({ endDate, filterQuery, + indexNames, skip, startDate, setQuery, @@ -32,6 +33,7 @@ export const CountriesQueryTabBody = ({ endDate, flowTarget, filterQuery, + indexNames, skip, startDate, type: networkModel.NetworkType.page, diff --git a/x-pack/plugins/security_solution/public/network/pages/navigation/dns_query_tab_body.tsx b/x-pack/plugins/security_solution/public/network/pages/navigation/dns_query_tab_body.tsx index 5adb78edbec8e..a8bae2509e0d6 100644 --- a/x-pack/plugins/security_solution/public/network/pages/navigation/dns_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/navigation/dns_query_tab_body.tsx @@ -45,6 +45,7 @@ const DnsQueryTabBodyComponent: React.FC = ({ deleteQuery, endDate, filterQuery, + indexNames, skip, startDate, setQuery, @@ -64,6 +65,7 @@ const DnsQueryTabBodyComponent: React.FC = ({ ] = useNetworkDns({ endDate, filterQuery, + indexNames, skip, startDate, type, @@ -88,6 +90,7 @@ const DnsQueryTabBodyComponent: React.FC = ({ endDate={endDate} filterQuery={filterQuery} id={HISTOGRAM_ID} + indexNames={indexNames} setQuery={setQuery} showLegend={true} startDate={startDate} diff --git a/x-pack/plugins/security_solution/public/network/pages/navigation/http_query_tab_body.tsx b/x-pack/plugins/security_solution/public/network/pages/navigation/http_query_tab_body.tsx index 3caff05734c1e..85d6b6daabd6c 100644 --- a/x-pack/plugins/security_solution/public/network/pages/navigation/http_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/navigation/http_query_tab_body.tsx @@ -19,6 +19,7 @@ const NetworkHttpTableManage = manageQuery(NetworkHttpTable); export const HttpQueryTabBody = ({ endDate, filterQuery, + indexNames, skip, startDate, setQuery, @@ -29,6 +30,7 @@ export const HttpQueryTabBody = ({ ] = useNetworkHttp({ endDate, filterQuery, + indexNames, skip, startDate, type: networkModel.NetworkType.page, diff --git a/x-pack/plugins/security_solution/public/network/pages/navigation/ips_query_tab_body.tsx b/x-pack/plugins/security_solution/public/network/pages/navigation/ips_query_tab_body.tsx index c83bf6ff80901..465b3347ce707 100644 --- a/x-pack/plugins/security_solution/public/network/pages/navigation/ips_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/navigation/ips_query_tab_body.tsx @@ -19,6 +19,7 @@ const NetworkTopNFlowTableManage = manageQuery(NetworkTopNFlowTable); export const IPsQueryTabBody = ({ endDate, filterQuery, + indexNames, skip, startDate, setQuery, @@ -31,6 +32,7 @@ export const IPsQueryTabBody = ({ endDate, flowTarget, filterQuery, + indexNames, skip, startDate, type: networkModel.NetworkType.page, diff --git a/x-pack/plugins/security_solution/public/network/pages/navigation/network_routes.tsx b/x-pack/plugins/security_solution/public/network/pages/navigation/network_routes.tsx index 2da56a30df7c7..7af474728c824 100644 --- a/x-pack/plugins/security_solution/public/network/pages/navigation/network_routes.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/navigation/network_routes.tsx @@ -33,6 +33,7 @@ export const NetworkRoutes = React.memo( isInitializing, from, indexPattern, + indexNames, setQuery, setAbsoluteRangeDatePicker, }) => { @@ -83,6 +84,7 @@ export const NetworkRoutes = React.memo( const commonProps = { startDate: from, endDate: to, + indexNames, skip: isInitializing, type, narrowDateRange, diff --git a/x-pack/plugins/security_solution/public/network/pages/navigation/tls_query_tab_body.tsx b/x-pack/plugins/security_solution/public/network/pages/navigation/tls_query_tab_body.tsx index 279891cc181e3..702a9e696220f 100644 --- a/x-pack/plugins/security_solution/public/network/pages/navigation/tls_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/navigation/tls_query_tab_body.tsx @@ -16,6 +16,7 @@ const TlsQueryTabBodyComponent: React.FC = ({ endDate, filterQuery, flowTarget, + indexNames, ip = '', setQuery, skip, @@ -29,6 +30,7 @@ const TlsQueryTabBodyComponent: React.FC = ({ endDate, filterQuery, flowTarget, + indexNames, ip, skip, startDate, diff --git a/x-pack/plugins/security_solution/public/network/pages/navigation/types.ts b/x-pack/plugins/security_solution/public/network/pages/navigation/types.ts index 2ef04d3371c0b..ed04fd01a7b89 100644 --- a/x-pack/plugins/security_solution/public/network/pages/navigation/types.ts +++ b/x-pack/plugins/security_solution/public/network/pages/navigation/types.ts @@ -22,6 +22,7 @@ interface QueryTabBodyProps extends Pick ({ +const mockProps = { networkPagePath: '', to, from, @@ -69,40 +69,42 @@ const getMockProps = () => ({ setQuery: jest.fn(), capabilitiesFetched: true, hasMlUserPermissions: true, -}); - +}; +const mockUseSourcererScope = useSourcererScope as jest.Mock; describe('rendering - rendering', () => { - test('it renders the Setup Instructions text when no index is available', async () => { - (useWithSource as jest.Mock).mockReturnValue({ + test('it renders the Setup Instructions text when no index is available', () => { + mockUseSourcererScope.mockReturnValue({ + selectedPatterns: [], indicesExist: false, }); const wrapper = mount( - + ); expect(wrapper.find('[data-test-subj="empty-page"]').exists()).toBe(true); }); - test('it DOES NOT render the Setup Instructions text when an index is available', async () => { - (useWithSource as jest.Mock).mockReturnValue({ + test('it DOES NOT render the Setup Instructions text when an index is available', () => { + mockUseSourcererScope.mockReturnValue({ + selectedPatterns: [], indicesExist: true, indexPattern: {}, }); const wrapper = mount( - + ); expect(wrapper.find('[data-test-subj="empty-page"]').exists()).toBe(false); }); - test('it should add the new filters after init', async () => { + test('it should add the new filters after init', () => { const newFilters: Filter[] = [ { query: { @@ -134,7 +136,8 @@ describe('rendering - rendering', () => { }, }, ]; - (useWithSource as jest.Mock).mockReturnValue({ + mockUseSourcererScope.mockReturnValue({ + selectedPatterns: [], indicesExist: true, indexPattern: { fields: [], title: 'title' }, }); @@ -150,7 +153,7 @@ describe('rendering - rendering', () => { const wrapper = mount( - + ); diff --git a/x-pack/plugins/security_solution/public/network/pages/network.tsx b/x-pack/plugins/security_solution/public/network/pages/network.tsx index e04350fd38df5..6aea771e49499 100644 --- a/x-pack/plugins/security_solution/public/network/pages/network.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/network.tsx @@ -19,12 +19,11 @@ import { HeaderPage } from '../../common/components/header_page'; import { LastEventTime } from '../../common/components/last_event_time'; import { SiemNavigation } from '../../common/components/navigation'; -import { KpiNetworkComponent } from '../components/kpi_network'; +import { NetworkKpiComponent } from '../components/kpi_network'; import { SiemSearchBar } from '../../common/components/search_bar'; import { WrapperPage } from '../../common/components/wrapper_page'; import { useFullScreen } from '../../common/containers/use_full_screen'; import { useGlobalTime } from '../../common/containers/use_global_time'; -import { useWithSource } from '../../common/containers/source'; import { LastEventIndexKey } from '../../graphql/types'; import { useKibana } from '../../common/lib/kibana'; import { convertToBuildEsQuery } from '../../common/lib/keury'; @@ -44,8 +43,7 @@ import { timelineSelectors } from '../../timelines/store/timeline'; import { TimelineId } from '../../../common/types/timeline'; import { timelineDefaults } from '../../timelines/store/timeline/defaults'; import { TimelineModel } from '../../timelines/store/timeline/model'; - -const sourceId = 'default'; +import { useSourcererScope } from '../../common/containers/sourcerer'; const NetworkComponent = React.memo( ({ @@ -84,7 +82,7 @@ const NetworkComponent = React.memo( [setAbsoluteRangeDatePicker] ); - const { indicesExist, indexPattern } = useWithSource(sourceId); + const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope(); const filterQuery = convertToBuildEsQuery({ config: esQuery.getEsQueryConfig(kibana.services.uiSettings), indexPattern, @@ -111,7 +109,13 @@ const NetworkComponent = React.memo( } + subtitle={ + + } title={i18n.PAGE_TITLE} /> @@ -125,13 +129,14 @@ const NetworkComponent = React.memo( - @@ -150,6 +155,7 @@ const NetworkComponent = React.memo( from={from} isInitializing={isInitializing} indexPattern={indexPattern} + indexNames={selectedPatterns} setQuery={setQuery} setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker} type={networkModel.NetworkType.page} diff --git a/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.test.tsx index e365ac38d31df..6f1b7e95e763d 100644 --- a/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.test.tsx @@ -29,7 +29,15 @@ const to = '2019-03-31T06:00:00.000Z'; describe('Alerts by category', () => { let wrapper: ReactWrapper; - + const testProps = { + deleteQuery: jest.fn(), + filters: [], + from, + indexNames: [], + indexPattern: mockIndexPattern, + setQuery: jest.fn(), + to, + }; describe('before loading data', () => { beforeAll(async () => { (useMatrixHistogram as jest.Mock).mockReturnValue([ @@ -44,14 +52,7 @@ describe('Alerts by category', () => { wrapper = mount( - + ); @@ -119,14 +120,7 @@ describe('Alerts by category', () => { wrapper = mount( - + ); diff --git a/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx b/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx index 1a2238c763bda..4d3b2dbf3f11f 100644 --- a/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx @@ -43,6 +43,7 @@ interface Props extends Pick = ({ from, hideHeaderChildren = false, indexPattern, + indexNames, query = DEFAULT_QUERY, setQuery, to, @@ -117,6 +119,7 @@ const AlertsByCategoryComponent: React.FC = ({ })} headerChildren={hideHeaderChildren ? null : alertsCountViewAlertsButton} id={ID} + indexNames={indexNames} setQuery={setQuery} startDate={from} {...alertsByCategoryHistogramConfigs} diff --git a/x-pack/plugins/security_solution/public/overview/components/event_counts/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/event_counts/index.test.tsx index f2d6b50326082..44cb7a65dbc5e 100644 --- a/x-pack/plugins/security_solution/public/overview/components/event_counts/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/event_counts/index.test.tsx @@ -20,11 +20,16 @@ describe('EventCounts', () => { const from = '2020-01-20T20:49:57.080Z'; const to = '2020-01-21T20:49:57.080Z'; + const testProps = { + from, + indexNames: [], + indexPattern: mockIndexPattern, + setQuery: jest.fn(), + to, + }; + test('it filters the `Host events` widget with a `host.name` `exists` filter', () => { - const wrapper = mount( - , - { wrappingComponent: TestProviders } - ); + const wrapper = mount(, { wrappingComponent: TestProviders }); expect( (wrapper.find('Memo(OverviewHostComponent)').first().props() as OverviewHostProps).filterQuery @@ -32,10 +37,7 @@ describe('EventCounts', () => { }); test('it filters the `Network events` widget with a `source.ip` or `destination.ip` `exists` filter', () => { - const wrapper = mount( - , - { wrappingComponent: TestProviders } - ); + const wrapper = mount(, { wrappingComponent: TestProviders }); expect( (wrapper.find('Memo(OverviewNetworkComponent)').first().props() as OverviewNetworkProps) diff --git a/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx b/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx index 23f5998f44111..6e47de68221c7 100644 --- a/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx @@ -31,6 +31,7 @@ const DEFAULT_QUERY: Query = { query: '', language: 'kuery' }; interface Props extends Pick { filters?: Filter[]; + indexNames: string[]; indexPattern: IIndexPattern; query?: Query; } @@ -38,6 +39,7 @@ interface Props extends Pick { const EventCountsComponent: React.FC = ({ filters = NO_FILTERS, from, + indexNames, indexPattern, query = DEFAULT_QUERY, setQuery, @@ -56,6 +58,7 @@ const EventCountsComponent: React.FC = ({ queries: [query], filters: [...filters, ...filterHostData], })} + indexNames={indexNames} startDate={from} setQuery={setQuery} /> @@ -72,6 +75,7 @@ const EventCountsComponent: React.FC = ({ queries: [query], filters: [...filters, ...filterNetworkData], })} + indexNames={indexNames} startDate={from} setQuery={setQuery} /> diff --git a/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx b/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx index 7025afde963f1..1fa3d8f4ef27a 100644 --- a/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx @@ -47,7 +47,7 @@ interface Props extends Pick = ({ from, headerChildren, indexPattern, - indexToAdd, + indexNames, onlyField, query = DEFAULT_QUERY, setAbsoluteRangeDatePickerTarget, @@ -164,7 +164,7 @@ const EventsByDatasetComponent: React.FC = ({ filterQuery={filterQuery} headerChildren={headerContent} id={uniqueQueryId} - indexToAdd={indexToAdd} + indexNames={indexNames} setAbsoluteRangeDatePickerTarget={setAbsoluteRangeDatePickerTarget} setQuery={setQuery} showSpacer={showSpacer} diff --git a/x-pack/plugins/security_solution/public/overview/components/host_overview/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/overview/components/host_overview/__snapshots__/index.test.tsx.snap index c9c34682519e2..47d45ab740dcf 100644 --- a/x-pack/plugins/security_solution/public/overview/components/host_overview/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/overview/components/host_overview/__snapshots__/index.test.tsx.snap @@ -192,8 +192,10 @@ exports[`Host Summary Component rendering it renders the default Host Summary 1` }, } } + docValueFields={Array []} endDate="2019-06-18T06:00:00.000Z" id="hostOverview" + indexNames={Array []} isLoadingAnomaliesData={false} loading={false} narrowDateRange={[MockFunction]} diff --git a/x-pack/plugins/security_solution/public/overview/components/host_overview/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/host_overview/index.test.tsx index 6bd0390d014a3..69bd053d707b9 100644 --- a/x-pack/plugins/security_solution/public/overview/components/host_overview/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/host_overview/index.test.tsx @@ -20,8 +20,10 @@ describe('Host Summary Component', () => { ( ({ anomaliesData, data, + docValueFields, endDate, id, isLoadingAnomaliesData, + indexNames, loading, narrowDateRange, startDate, @@ -91,7 +95,9 @@ export const HostOverview = React.memo( description: data.host != null && data.host.name && data.host.name.length ? ( ) : ( @@ -103,7 +109,9 @@ export const HostOverview = React.memo( description: data.host != null && data.host.name && data.host.name.length ? ( ) : ( @@ -111,7 +119,7 @@ export const HostOverview = React.memo( ), }, ], - [data] + [data, docValueFields, indexNames] ); const firstColumn = useMemo( () => diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.test.tsx index b932add7afc2c..8f0e9a56254ec 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.test.tsx @@ -27,7 +27,12 @@ jest.mock('../../../common/components/link_to'); const startDate = '2020-01-20T20:49:57.080Z'; const endDate = '2020-01-21T20:49:57.080Z'; - +const testProps = { + endDate, + indexNames: [], + setQuery: jest.fn(), + startDate, +}; const MOCKED_RESPONSE = { overviewHost: { auditbeatAuditd: 1, @@ -79,7 +84,7 @@ describe('OverviewHost', () => { test('it renders the expected widget title', () => { const wrapper = mount( - + ); @@ -92,7 +97,7 @@ describe('OverviewHost', () => { useHostOverviewMock.mockReturnValueOnce([true, { overviewHost: {} }]); const wrapper = mount( - + ); @@ -102,7 +107,7 @@ describe('OverviewHost', () => { test('it renders the expected event count in the subtitle after loading events', async () => { const wrapper = mount( - + ); diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx index 3f35d0abbaa85..f92f004bd2448 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx @@ -22,11 +22,11 @@ import { InspectButtonContainer } from '../../../common/components/inspect'; import { GlobalTimeArgs } from '../../../common/containers/use_global_time'; import { SecurityPageName } from '../../../app/types'; import { LinkButton } from '../../../common/components/links'; -import { Sourcerer } from '../../../common/components/sourcerer'; export interface OwnProps { startDate: GlobalTimeArgs['from']; endDate: GlobalTimeArgs['to']; + indexNames: string[]; filterQuery?: ESQuery | string; setQuery: GlobalTimeArgs['setQuery']; } @@ -37,6 +37,7 @@ export type OverviewHostProps = OwnProps; const OverviewHostComponent: React.FC = ({ endDate, filterQuery, + indexNames, startDate, setQuery, }) => { @@ -47,6 +48,7 @@ const OverviewHostComponent: React.FC = ({ const [loading, { overviewHost, id, inspect, refetch }] = useHostOverview({ endDate, filterQuery, + indexNames, startDate, }); @@ -109,10 +111,7 @@ const OverviewHostComponent: React.FC = ({ /> } > - <> - - {hostPageButton} - + <>{hostPageButton} { describe('rendering', () => { test('it renders the default OverviewHostStats', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); }); @@ -22,7 +22,7 @@ describe('Overview Host Stat Data', () => { test('it does NOT show loading indicator when loading is false', () => { const wrapper = mount( - + ); @@ -42,7 +42,7 @@ describe('Overview Host Stat Data', () => { test('it shows loading indicator when loading is true', () => { const wrapper = mount( - + ); diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_host_stats/index.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_host_stats/index.tsx index 92250ed3c549b..ef595476d8a94 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_host_stats/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_host_stats/index.tsx @@ -9,16 +9,18 @@ import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; import styled from 'styled-components'; -import { OverviewHostData } from '../../../graphql/types'; +import { HostsOverviewStrategyResponse } from '../../../../common/search_strategy'; import { FormattedStat, StatGroup } from '../types'; import { StatValue } from '../stat_value'; interface OverviewHostProps { - data: OverviewHostData; + data: HostsOverviewStrategyResponse['overviewHost']; loading: boolean; } -export const getOverviewHostStats = (data: OverviewHostData): FormattedStat[] => [ +export const getOverviewHostStats = ( + data: HostsOverviewStrategyResponse['overviewHost'] +): FormattedStat[] => [ { count: data.auditbeatAuditd ?? 0, title: ( diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_host_stats/mock.ts b/x-pack/plugins/security_solution/public/overview/components/overview_host_stats/mock.ts index 63b3a484c1eaa..986d02faac37a 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_host_stats/mock.ts +++ b/x-pack/plugins/security_solution/public/overview/components/overview_host_stats/mock.ts @@ -4,25 +4,23 @@ * you may not use this file except in compliance with the Elastic License. */ -import { OverviewHostData } from '../../../graphql/types'; +import { HostsOverviewStrategyResponse } from '../../../../common/search_strategy'; -export const mockData: { OverviewHost: OverviewHostData } = { - OverviewHost: { - auditbeatAuditd: 73847, - auditbeatFIM: 107307, - auditbeatLogin: 60015, - auditbeatPackage: 2003, - auditbeatProcess: 1200, - auditbeatUser: 1979, - endgameDns: 39123, - endgameFile: 39456, - endgameImageLoad: 39789, - endgameNetwork: 39101112, - endgameProcess: 39131415, - endgameRegistry: 39161718, - endgameSecurity: 39202122, - filebeatSystemModule: 568, - winlogbeatSecurity: 195929, - winlogbeatMWSysmonOperational: 101070, - }, +export const mockData: HostsOverviewStrategyResponse['overviewHost'] = { + auditbeatAuditd: 73847, + auditbeatFIM: 107307, + auditbeatLogin: 60015, + auditbeatPackage: 2003, + auditbeatProcess: 1200, + auditbeatUser: 1979, + endgameDns: 39123, + endgameFile: 39456, + endgameImageLoad: 39789, + endgameNetwork: 39101112, + endgameProcess: 39131415, + endgameRegistry: 39161718, + endgameSecurity: 39202122, + filebeatSystemModule: 568, + winlogbeatSecurity: 195929, + winlogbeatMWSysmonOperational: 101070, }; diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx index f67287ea4b9e2..c4c0b2e5e255e 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx @@ -43,6 +43,12 @@ jest.mock('../../../common/lib/kibana', () => { const startDate = '2020-01-20T20:49:57.080Z'; const endDate = '2020-01-21T20:49:57.080Z'; +const defaultProps = { + endDate, + startDate, + setQuery: jest.fn(), + indexNames: [], +}; const MOCKED_RESPONSE = { overviewNetwork: { @@ -88,7 +94,7 @@ describe('OverviewNetwork', () => { test('it renders the expected widget title', () => { const wrapper = mount( - + ); @@ -101,7 +107,7 @@ describe('OverviewNetwork', () => { useNetworkOverviewMock.mockReturnValueOnce([true, { overviewNetwork: {} }]); const wrapper = mount( - + ); @@ -111,7 +117,7 @@ describe('OverviewNetwork', () => { test('it renders the expected event count in the subtitle after loading events', async () => { const wrapper = mount( - + ); @@ -123,7 +129,7 @@ describe('OverviewNetwork', () => { it('it renders View Network', () => { const wrapper = mount( - + ); @@ -133,7 +139,7 @@ describe('OverviewNetwork', () => { it('when click on View Network we call navigateToApp to make sure to navigate to right page', () => { const wrapper = mount( - + ); diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx index 089bed3c67808..178a752d1286f 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx @@ -30,6 +30,7 @@ export interface OverviewNetworkProps { startDate: GlobalTimeArgs['from']; endDate: GlobalTimeArgs['to']; filterQuery?: ESQuery | string; + indexNames: string[]; setQuery: GlobalTimeArgs['setQuery']; } @@ -38,6 +39,7 @@ const OverviewNetworkStatsManage = manageQuery(OverviewNetworkStats); const OverviewNetworkComponent: React.FC = ({ endDate, filterQuery, + indexNames, startDate, setQuery, }) => { @@ -48,6 +50,7 @@ const OverviewNetworkComponent: React.FC = ({ const [loading, { overviewNetwork, id, inspect, refetch }] = useNetworkOverview({ endDate, filterQuery, + indexNames, startDate, }); diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_network_stats/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_network_stats/index.test.tsx index 0add7c1a02047..2f801ae1f3623 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_network_stats/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_network_stats/index.test.tsx @@ -14,9 +14,7 @@ import { TestProviders } from '../../../common/mock/test_providers'; describe('Overview Network Stat Data', () => { describe('rendering', () => { test('it renders the default OverviewNetworkStats', () => { - const wrapper = shallow( - - ); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); }); @@ -24,7 +22,7 @@ describe('Overview Network Stat Data', () => { test('it does NOT show loading indicator when loading is false', () => { const wrapper = mount( - + ); @@ -45,7 +43,7 @@ describe('Overview Network Stat Data', () => { test('it shows the loading indicator when loading is true', () => { const wrapper = mount( - + ); diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_network_stats/index.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_network_stats/index.tsx index d3e16af7115ac..c6ad56b7243d4 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_network_stats/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_network_stats/index.tsx @@ -9,16 +9,18 @@ import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; import styled from 'styled-components'; -import { OverviewNetworkData } from '../../../graphql/types'; +import { NetworkOverviewStrategyResponse } from '../../../../common/search_strategy'; import { FormattedStat, StatGroup } from '../types'; import { StatValue } from '../stat_value'; interface OverviewNetworkProps { - data: OverviewNetworkData; + data: NetworkOverviewStrategyResponse['overviewNetwork']; loading: boolean; } -export const getOverviewNetworkStats = (data: OverviewNetworkData): FormattedStat[] => [ +export const getOverviewNetworkStats = ( + data: NetworkOverviewStrategyResponse['overviewNetwork'] +): FormattedStat[] => [ { count: data.auditbeatSocket ?? 0, title: ( diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_network_stats/mock.ts b/x-pack/plugins/security_solution/public/overview/components/overview_network_stats/mock.ts index f55d6a1577ccd..1eb337f1ea454 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_network_stats/mock.ts +++ b/x-pack/plugins/security_solution/public/overview/components/overview_network_stats/mock.ts @@ -4,18 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { OverviewNetworkData } from '../../../graphql/types'; +import { NetworkOverviewStrategyResponse } from '../../../../common/search_strategy'; -export const mockData: { OverviewNetwork: OverviewNetworkData } = { - OverviewNetwork: { - auditbeatSocket: 12, - filebeatCisco: 999, - filebeatNetflow: 7777, - filebeatPanw: 66, - filebeatSuricata: 60015, - filebeatZeek: 2003, - packetbeatDNS: 10277307, - packetbeatFlow: 16, - packetbeatTLS: 3400000, - }, +export const mockData: NetworkOverviewStrategyResponse['overviewNetwork'] = { + auditbeatSocket: 12, + filebeatCisco: 999, + filebeatNetflow: 7777, + filebeatPanw: 66, + filebeatSuricata: 60015, + filebeatZeek: 2003, + packetbeatDNS: 10277307, + packetbeatFlow: 16, + packetbeatTLS: 3400000, }; diff --git a/x-pack/plugins/security_solution/public/overview/components/recent_cases/no_cases/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/recent_cases/no_cases/index.test.tsx index 446679ae26d9e..8a0d2f4f16202 100644 --- a/x-pack/plugins/security_solution/public/overview/components/recent_cases/no_cases/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/recent_cases/no_cases/index.test.tsx @@ -33,7 +33,7 @@ describe('RecentCases', () => { wrapper.find(`[data-test-subj="no-cases-create-case"]`).first().simulate('click'); expect(navigateToApp).toHaveBeenCalledWith('securitySolution:case', { path: - "/create?timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "/create?sourcerer=(default:!('apm-*-transaction*','auditbeat-*','endgame-*','filebeat-*','logs-*','packetbeat-*','winlogbeat-*'))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", }); }); }); diff --git a/x-pack/plugins/security_solution/public/overview/containers/overview_host/index.gql_query.ts b/x-pack/plugins/security_solution/public/overview/containers/overview_host/index.gql_query.ts deleted file mode 100644 index 6f17bf6915aa4..0000000000000 --- a/x-pack/plugins/security_solution/public/overview/containers/overview_host/index.gql_query.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import gql from 'graphql-tag'; - -export const overviewHostQuery = gql` - query GetOverviewHostQuery( - $sourceId: ID! - $timerange: TimerangeInput! - $filterQuery: String - $defaultIndex: [String!]! - $inspect: Boolean! - ) { - source(id: $sourceId) { - id - OverviewHost(timerange: $timerange, filterQuery: $filterQuery, defaultIndex: $defaultIndex) { - auditbeatAuditd - auditbeatFIM - auditbeatLogin - auditbeatPackage - auditbeatProcess - auditbeatUser - endgameDns - endgameFile - endgameImageLoad - endgameNetwork - endgameProcess - endgameRegistry - endgameSecurity - filebeatSystemModule - winlogbeatSecurity - winlogbeatMWSysmonOperational - inspect @include(if: $inspect) { - dsl - response - } - } - } - } -`; diff --git a/x-pack/plugins/security_solution/public/overview/containers/overview_host/index.tsx b/x-pack/plugins/security_solution/public/overview/containers/overview_host/index.tsx index e011e6c7b6b65..946cd33088a45 100644 --- a/x-pack/plugins/security_solution/public/overview/containers/overview_host/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/containers/overview_host/index.tsx @@ -5,22 +5,23 @@ */ import { noop } from 'lodash/fp'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import deepEqual from 'fast-deep-equal'; -import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import { HostsQueries, HostOverviewRequestOptions, - HostOverviewStrategyResponse, + HostsOverviewStrategyResponse, } from '../../../../common/search_strategy/security_solution'; import { useKibana } from '../../../common/lib/kibana'; import { inputsModel } from '../../../common/store/inputs'; import { createFilter } from '../../../common/containers/helpers'; import { ESQuery } from '../../../../common/typed_json'; -import { useManageSource } from '../../../common/containers/sourcerer'; -import { SOURCERER_FEATURE_FLAG_ON } from '../../../common/containers/sourcerer/constants'; -import { AbortError } from '../../../../../../../src/plugins/data/common'; +import { + AbortError, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../src/plugins/data/common'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; import * as i18n from './translations'; @@ -31,13 +32,14 @@ export interface HostOverviewArgs { id: string; inspect: InspectResponse; isInspected: boolean; - overviewHost: HostOverviewStrategyResponse['overviewHost']; + overviewHost: HostsOverviewStrategyResponse['overviewHost']; refetch: inputsModel.Refetch; } interface UseHostOverview { filterQuery?: ESQuery | string; endDate: string; + indexNames: string[]; skip?: boolean; startDate: string; } @@ -45,23 +47,16 @@ interface UseHostOverview { export const useHostOverview = ({ filterQuery, endDate, + indexNames, skip = false, startDate, }: UseHostOverview): [boolean, HostOverviewArgs] => { - const { data, notifications, uiSettings } = useKibana().services; - const { activeSourceGroupId, getManageSourceGroupById } = useManageSource(); - const { indexPatterns } = useMemo(() => getManageSourceGroupById(activeSourceGroupId), [ - getManageSourceGroupById, - activeSourceGroupId, - ]); - const uiDefaultIndexPatterns = uiSettings.get(DEFAULT_INDEX_KEY); - const defaultIndex = SOURCERER_FEATURE_FLAG_ON ? indexPatterns : uiDefaultIndexPatterns; - + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const [loading, setLoading] = useState(false); const [overviewHostRequest, setHostRequest] = useState({ - defaultIndex, + defaultIndex: indexNames, factoryQueryType: HostsQueries.overview, filterQuery: createFilter(filterQuery), timerange: { @@ -90,13 +85,13 @@ export const useHostOverview = ({ setLoading(true); const searchSubscription$ = data.search - .search(request, { + .search(request, { strategy: 'securitySolutionSearchStrategy', abortSignal: abortCtrl.current.signal, }) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setHostOverviewResponse((prevResponse) => ({ @@ -107,7 +102,7 @@ export const useHostOverview = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -141,7 +136,7 @@ export const useHostOverview = ({ setHostRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, filterQuery: createFilter(filterQuery), timerange: { interval: '12h', @@ -154,7 +149,7 @@ export const useHostOverview = ({ } return prevRequest; }); - }, [defaultIndex, endDate, filterQuery, skip, startDate]); + }, [indexNames, endDate, filterQuery, skip, startDate]); useEffect(() => { overviewHostSearch(overviewHostRequest); diff --git a/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.gql_query.ts b/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.gql_query.ts deleted file mode 100644 index d40ab900b91a7..0000000000000 --- a/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.gql_query.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; - * you may not use this file except in compliance with the Elastic License. - */ - -import gql from 'graphql-tag'; - -export const overviewNetworkQuery = gql` - query GetOverviewNetworkQuery( - $sourceId: ID! - $timerange: TimerangeInput! - $filterQuery: String - $defaultIndex: [String!]! - $inspect: Boolean! - ) { - source(id: $sourceId) { - id - OverviewNetwork( - timerange: $timerange - filterQuery: $filterQuery - defaultIndex: $defaultIndex - ) { - auditbeatSocket - filebeatCisco - filebeatNetflow - filebeatPanw - filebeatSuricata - filebeatZeek - packetbeatDNS - packetbeatFlow - packetbeatTLS - inspect @include(if: $inspect) { - dsl - response - } - } - } - } -`; diff --git a/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx b/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx index c61606e0c31dd..588fb1f08ef6f 100644 --- a/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx @@ -8,7 +8,6 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import deepEqual from 'fast-deep-equal'; -import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import { NetworkQueries, NetworkOverviewRequestOptions, @@ -18,7 +17,11 @@ import { useKibana } from '../../../common/lib/kibana'; import { inputsModel } from '../../../common/store/inputs'; import { createFilter } from '../../../common/containers/helpers'; import { ESQuery } from '../../../../common/typed_json'; -import { AbortError } from '../../../../../../../src/plugins/data/common'; +import { + AbortError, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../src/plugins/data/common'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; import * as i18n from './translations'; @@ -36,6 +39,7 @@ export interface NetworkOverviewArgs { interface UseNetworkOverview { filterQuery?: ESQuery | string; endDate: string; + indexNames: string[]; skip?: boolean; startDate: string; } @@ -43,16 +47,16 @@ interface UseNetworkOverview { export const useNetworkOverview = ({ filterQuery, endDate, + indexNames, skip = false, startDate, }: UseNetworkOverview): [boolean, NetworkOverviewArgs] => { - const { data, notifications, uiSettings } = useKibana().services; - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const [loading, setLoading] = useState(false); const [overviewNetworkRequest, setNetworkRequest] = useState({ - defaultIndex, + defaultIndex: indexNames, factoryQueryType: NetworkQueries.overview, filterQuery: createFilter(filterQuery), timerange: { @@ -87,7 +91,7 @@ export const useNetworkOverview = ({ }) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setNetworkOverviewResponse((prevResponse) => ({ @@ -98,7 +102,7 @@ export const useNetworkOverview = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -132,7 +136,7 @@ export const useNetworkOverview = ({ setNetworkRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex, + defaultIndex: indexNames, filterQuery: createFilter(filterQuery), timerange: { interval: '12h', @@ -145,7 +149,7 @@ export const useNetworkOverview = ({ } return prevRequest; }); - }, [defaultIndex, endDate, filterQuery, skip, startDate]); + }, [indexNames, endDate, filterQuery, skip, startDate]); useEffect(() => { overviewNetworkSearch(overviewNetworkRequest); diff --git a/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx b/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx index 74225c4e4f823..222b9e008ddd7 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx @@ -10,16 +10,18 @@ import { MemoryRouter } from 'react-router-dom'; import '../../common/mock/match_media'; import { TestProviders } from '../../common/mock'; -import { useWithSource } from '../../common/containers/source'; import { useMessagesStorage, UseMessagesStorage, } from '../../common/containers/local_storage/use_messages_storage'; import { Overview } from './index'; import { useIngestEnabledCheck } from '../../common/hooks/endpoint/ingest_enabled'; +import { useSourcererScope } from '../../common/containers/sourcerer'; +import { useFetchIndex } from '../../common/containers/source'; jest.mock('../../common/lib/kibana'); jest.mock('../../common/containers/source'); +jest.mock('../../common/containers/sourcerer'); jest.mock('../../common/containers/use_global_time', () => ({ useGlobalTime: jest.fn().mockReturnValue({ from: '2020-07-07T08:20:18.966Z', @@ -49,65 +51,29 @@ const endpointNoticeMessage = (hasMessageValue: boolean) => { clearAllMessages: () => undefined, }; }; - +const mockUseSourcererScope = useSourcererScope as jest.Mock; +const mockUseIngestEnabledCheck = useIngestEnabledCheck as jest.Mock; +const mockUseFetchIndex = useFetchIndex as jest.Mock; +const mockUseMessagesStorage: jest.Mock = useMessagesStorage as jest.Mock; describe('Overview', () => { + beforeEach(() => { + mockUseFetchIndex.mockReturnValue([ + false, + { + indexExists: true, + }, + ]); + }); describe('rendering', () => { - describe('when no index is available', () => { - beforeEach(() => { - (useWithSource as jest.Mock).mockReturnValue({ - indicesExist: false, - }); - (useIngestEnabledCheck as jest.Mock).mockReturnValue({ allEnabled: false }); - const mockuseMessagesStorage: jest.Mock = useMessagesStorage as jest.Mock< - UseMessagesStorage - >; - mockuseMessagesStorage.mockImplementation(() => endpointNoticeMessage(false)); - }); - - it('renders the Setup Instructions text', () => { - const wrapper = mount( - - - - - - ); - expect(wrapper.find('[data-test-subj="empty-page"]').exists()).toBe(true); - }); - - it('does not show Endpoint get ready button when ingest is not enabled', () => { - const wrapper = mount( - - - - - - ); - expect(wrapper.find('[data-test-subj="empty-page-endpoint-action"]').exists()).toBe(false); - }); - - it('shows Endpoint get ready button when ingest is enabled', () => { - (useIngestEnabledCheck as jest.Mock).mockReturnValue({ allEnabled: true }); - const wrapper = mount( - - - - - - ); - expect(wrapper.find('[data-test-subj="empty-page-endpoint-action"]').exists()).toBe(true); - }); - }); - - it('it DOES NOT render the Getting started text when an index is available', () => { - (useWithSource as jest.Mock).mockReturnValue({ + test('it DOES NOT render the Getting started text when an index is available', () => { + mockUseSourcererScope.mockReturnValue({ + selectedPatterns: [], indicesExist: true, indexPattern: {}, }); - const mockuseMessagesStorage: jest.Mock = useMessagesStorage as jest.Mock; - mockuseMessagesStorage.mockImplementation(() => endpointNoticeMessage(false)); - (useIngestEnabledCheck as jest.Mock).mockReturnValue({ allEnabled: true }); + mockUseMessagesStorage.mockImplementation(() => endpointNoticeMessage(false)); + mockUseIngestEnabledCheck.mockReturnValue({ allEnabled: true }); const wrapper = mount( @@ -122,19 +88,20 @@ describe('Overview', () => { }); test('it DOES render the Endpoint banner when the endpoint index is NOT available AND storage is NOT set', () => { - (useWithSource as jest.Mock).mockReturnValueOnce({ + mockUseFetchIndex.mockReturnValue([ + false, + { + indexExists: false, + }, + ]); + mockUseSourcererScope.mockReturnValue({ + selectedPatterns: [], indicesExist: true, indexPattern: {}, }); - (useWithSource as jest.Mock).mockReturnValueOnce({ - indicesExist: false, - indexPattern: {}, - }); - - const mockuseMessagesStorage: jest.Mock = useMessagesStorage as jest.Mock; - mockuseMessagesStorage.mockImplementation(() => endpointNoticeMessage(false)); - (useIngestEnabledCheck as jest.Mock).mockReturnValue({ allEnabled: true }); + mockUseMessagesStorage.mockImplementation(() => endpointNoticeMessage(false)); + mockUseIngestEnabledCheck.mockReturnValue({ allEnabled: true }); const wrapper = mount( @@ -149,19 +116,20 @@ describe('Overview', () => { }); test('it does NOT render the Endpoint banner when the endpoint index is NOT available but storage is set', () => { - (useWithSource as jest.Mock).mockReturnValueOnce({ + mockUseFetchIndex.mockReturnValue([ + false, + { + indexExists: false, + }, + ]); + mockUseSourcererScope.mockReturnValueOnce({ + selectedPatterns: [], indicesExist: true, indexPattern: {}, }); - (useWithSource as jest.Mock).mockReturnValueOnce({ - indicesExist: false, - indexPattern: {}, - }); - - const mockuseMessagesStorage: jest.Mock = useMessagesStorage as jest.Mock; - mockuseMessagesStorage.mockImplementation(() => endpointNoticeMessage(true)); - (useIngestEnabledCheck as jest.Mock).mockReturnValue({ allEnabled: true }); + mockUseMessagesStorage.mockImplementation(() => endpointNoticeMessage(true)); + mockUseIngestEnabledCheck.mockReturnValue({ allEnabled: true }); const wrapper = mount( @@ -176,14 +144,14 @@ describe('Overview', () => { }); test('it does NOT render the Endpoint banner when the endpoint index is available AND storage is set', () => { - (useWithSource as jest.Mock).mockReturnValue({ - indicesExist: true, + mockUseSourcererScope.mockReturnValue({ + selectedPatterns: [], + indexExists: true, indexPattern: {}, }); - const mockuseMessagesStorage: jest.Mock = useMessagesStorage as jest.Mock; - mockuseMessagesStorage.mockImplementation(() => endpointNoticeMessage(true)); - (useIngestEnabledCheck as jest.Mock).mockReturnValue({ allEnabled: true }); + mockUseMessagesStorage.mockImplementation(() => endpointNoticeMessage(true)); + mockUseIngestEnabledCheck.mockReturnValue({ allEnabled: true }); const wrapper = mount( @@ -198,14 +166,14 @@ describe('Overview', () => { }); test('it does NOT render the Endpoint banner when an index IS available but storage is NOT set', () => { - (useWithSource as jest.Mock).mockReturnValue({ + mockUseSourcererScope.mockReturnValue({ + selectedPatterns: [], indicesExist: true, indexPattern: {}, }); - const mockuseMessagesStorage: jest.Mock = useMessagesStorage as jest.Mock; - mockuseMessagesStorage.mockImplementation(() => endpointNoticeMessage(false)); - (useIngestEnabledCheck as jest.Mock).mockReturnValue({ allEnabled: true }); + mockUseMessagesStorage.mockImplementation(() => endpointNoticeMessage(false)); + mockUseIngestEnabledCheck.mockReturnValue({ allEnabled: true }); const wrapper = mount( @@ -214,19 +182,20 @@ describe('Overview', () => { ); + wrapper.update(); expect(wrapper.find('[data-test-subj="endpoint-prompt-banner"]').exists()).toBe(false); wrapper.unmount(); }); test('it does NOT render the Endpoint banner when Ingest is NOT available', () => { - (useWithSource as jest.Mock).mockReturnValue({ + mockUseSourcererScope.mockReturnValue({ + selectedPatterns: [], indicesExist: true, indexPattern: {}, }); - const mockuseMessagesStorage: jest.Mock = useMessagesStorage as jest.Mock; - mockuseMessagesStorage.mockImplementation(() => endpointNoticeMessage(true)); - (useIngestEnabledCheck as jest.Mock).mockReturnValue({ allEnabled: false }); + mockUseMessagesStorage.mockImplementation(() => endpointNoticeMessage(true)); + mockUseIngestEnabledCheck.mockReturnValue({ allEnabled: false }); const wrapper = mount( @@ -239,5 +208,50 @@ describe('Overview', () => { expect(wrapper.find('[data-test-subj="endpoint-prompt-banner"]').exists()).toBe(false); wrapper.unmount(); }); + + describe('when no index is available', () => { + beforeEach(() => { + mockUseSourcererScope.mockReturnValue({ + selectedPatterns: [], + indicesExist: false, + }); + mockUseIngestEnabledCheck.mockReturnValue({ allEnabled: false }); + mockUseMessagesStorage.mockImplementation(() => endpointNoticeMessage(false)); + }); + + it('renders the Setup Instructions text', () => { + const wrapper = mount( + + + + + + ); + expect(wrapper.find('[data-test-subj="empty-page"]').exists()).toBe(true); + }); + + it('does not show Endpoint get ready button when ingest is not enabled', () => { + const wrapper = mount( + + + + + + ); + expect(wrapper.find('[data-test-subj="empty-page-endpoint-action"]').exists()).toBe(false); + }); + + it('shows Endpoint get ready button when ingest is enabled', () => { + mockUseIngestEnabledCheck.mockReturnValue({ allEnabled: true }); + const wrapper = mount( + + + + + + ); + expect(wrapper.find('[data-test-subj="empty-page-endpoint-action"]').exists()).toBe(true); + }); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx index 520fd6c459705..5a3b4ec384686 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx @@ -15,7 +15,7 @@ import { FiltersGlobal } from '../../common/components/filters_global'; import { SiemSearchBar } from '../../common/components/search_bar'; import { WrapperPage } from '../../common/components/wrapper_page'; import { useGlobalTime } from '../../common/containers/use_global_time'; -import { useWithSource } from '../../common/containers/source'; +import { useFetchIndex } from '../../common/containers/source'; import { EventsByDataset } from '../components/events_by_dataset'; import { EventCounts } from '../components/event_counts'; @@ -30,6 +30,9 @@ import { EndpointNotice } from '../components/endpoint_notice'; import { useMessagesStorage } from '../../common/containers/local_storage/use_messages_storage'; import { ENDPOINT_METADATA_INDEX } from '../../../common/constants'; import { useIngestEnabledCheck } from '../../common/hooks/endpoint/ingest_enabled'; +import { useSourcererScope } from '../../common/containers/sourcerer'; +import { Sourcerer } from '../../common/components/sourcerer'; +import { SourcererScopeName } from '../../common/store/sourcerer/model'; const DEFAULT_QUERY: Query = { query: '', language: 'kuery' }; const NO_FILTERS: Filter[] = []; @@ -43,17 +46,13 @@ const OverviewComponent: React.FC = ({ query = DEFAULT_QUERY, setAbsoluteRangeDatePicker, }) => { + const { from, deleteQuery, setQuery, to } = useGlobalTime(); + const { indicesExist, indexPattern, selectedPatterns } = useSourcererScope(); + const endpointMetadataIndex = useMemo(() => { return [ENDPOINT_METADATA_INDEX]; }, []); - - const { from, deleteQuery, setQuery, to } = useGlobalTime(); - const { indicesExist, indexPattern } = useWithSource(); - const { indicesExist: metadataIndexExists } = useWithSource( - 'default', - endpointMetadataIndex, - true - ); + const [, { indexExists: metadataIndexExists }] = useFetchIndex(endpointMetadataIndex, true); const { addMessage, hasMessage } = useMessagesStorage(); const hasDismissEndpointNoticeMessage: boolean = useMemo( () => hasMessage('management', 'dismissEndpointNotice'), @@ -81,6 +80,7 @@ const OverviewComponent: React.FC = ({ )} + @@ -107,6 +107,7 @@ const OverviewComponent: React.FC = ({ filters={filters} from={from} indexPattern={indexPattern} + indexNames={selectedPatterns} query={query} setQuery={setQuery} to={to} @@ -119,6 +120,7 @@ const OverviewComponent: React.FC = ({ filters={filters} from={from} indexPattern={indexPattern} + indexNames={selectedPatterns} query={query} setQuery={setQuery} to={to} @@ -129,6 +131,7 @@ const OverviewComponent: React.FC = ({ { - const docLinks = useKibana().services.docLinks; - + const { docLinks, http } = useKibana().services; + const basePath = http.basePath.get(); return ( @@ -39,7 +39,7 @@ export const Summary = React.memo(() => { ), data: ( - + { private kibanaVersion: string; @@ -385,15 +390,32 @@ export class Plugin implements IPlugin( + { indices: defaultIndicesName, onlyCheckIfIndicesExist: false }, + { + strategy: 'securitySolutionIndexFields', + } + ) + .toPromise(), + ]); - const { - detectionsSubPlugin, - hostsSubPlugin, - networkSubPlugin, - timelinesSubPlugin, - managementSubPlugin, - } = await this.downloadSubPlugins(); const { apolloClient } = composeLibs(coreStart); const appLibs: AppObservableLibs = { apolloClient, kibana: coreStart }; const libs$ = new BehaviorSubject(appLibs); @@ -417,12 +439,18 @@ export class Plugin implements IPlugin { return Promise.resolve({ entityID, - events: [ - mockEndpointEvent({ - entityID, - name: 'event', - timestamp: 0, - }), - ], + events: [], nextEvent: null, }); }, diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_in_index_called_awesome_index.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_in_index_called_awesome_index.ts index ec0fa93485783..86450b25eb1da 100644 --- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_in_index_called_awesome_index.ts +++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_in_index_called_awesome_index.ts @@ -61,7 +61,7 @@ export function noAncestorsTwoChildenInIndexCalledAwesomeIndex(): { events: [ mockEndpointEvent({ entityID, - name: 'event', + processName: 'event', timestamp: 0, }), ], diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin.ts index 95ec0cd1a5f77..ec773a09ae8e0 100644 --- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin.ts +++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin.ts @@ -66,7 +66,7 @@ export function noAncestorsTwoChildrenWithRelatedEventsOnOrigin(): { entityID, events, nextEvent: null, - } as ResolverRelatedEvents); + }); }, /** diff --git a/x-pack/plugins/security_solution/public/resolver/index.ts b/x-pack/plugins/security_solution/public/resolver/index.ts index 409f82c9d1560..08a3722f40493 100644 --- a/x-pack/plugins/security_solution/public/resolver/index.ts +++ b/x-pack/plugins/security_solution/public/resolver/index.ts @@ -7,7 +7,7 @@ import { Provider } from 'react-redux'; import { ResolverPluginSetup } from './types'; import { resolverStoreFactory } from './store/index'; import { ResolverWithoutProviders } from './view/resolver_without_providers'; -import { noAncestorsTwoChildren } from './data_access_layer/mocks/no_ancestors_two_children'; +import { noAncestorsTwoChildrenWithRelatedEventsOnOrigin } from './data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin'; /** * These exports are used by the plugin 'resolverTest' defined in x-pack's plugin_functional suite. @@ -23,7 +23,7 @@ export function resolverPluginSetup(): ResolverPluginSetup { ResolverWithoutProviders, mocks: { dataAccessLayer: { - noAncestorsTwoChildren, + noAncestorsTwoChildrenWithRelatedEventsOnOrigin, }, }, }; diff --git a/x-pack/plugins/security_solution/public/resolver/mocks/endpoint_event.ts b/x-pack/plugins/security_solution/public/resolver/mocks/endpoint_event.ts index 083f6b8baa59f..d19ca285ff3ff 100644 --- a/x-pack/plugins/security_solution/public/resolver/mocks/endpoint_event.ts +++ b/x-pack/plugins/security_solution/public/resolver/mocks/endpoint_event.ts @@ -4,31 +4,36 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EndpointEvent } from '../../../common/endpoint/types'; +import { SafeResolverEvent } from '../../../common/endpoint/types'; /** * Simple mock endpoint event that works for tree layouts. */ export function mockEndpointEvent({ entityID, - name, - parentEntityId, - timestamp, - lifecycleType, + processName = 'process name', + parentEntityID, + timestamp = 0, + eventType = 'start', + eventCategory = 'process', pid = 0, + eventID = 'event id', }: { entityID: string; - name: string; - parentEntityId?: string; - timestamp: number; - lifecycleType?: string; + processName?: string; + parentEntityID?: string; + timestamp?: number; + eventType?: string; + eventCategory?: string; pid?: number; -}): EndpointEvent { + eventID?: string; +}): SafeResolverEvent { return { '@timestamp': timestamp, event: { - type: lifecycleType ? lifecycleType : 'start', - category: 'process', + type: eventType, + category: eventCategory, + id: eventID, }, agent: { id: 'agent.id', @@ -46,15 +51,15 @@ export function mockEndpointEvent({ entity_id: entityID, executable: 'executable', args: 'args', - name, + name: processName, pid, hash: { md5: 'hash.md5', }, parent: { pid: 0, - entity_id: parentEntityId, + entity_id: parentEntityID, }, }, - } as EndpointEvent; + }; } diff --git a/x-pack/plugins/security_solution/public/resolver/mocks/resolver_tree.ts b/x-pack/plugins/security_solution/public/resolver/mocks/resolver_tree.ts index 8bd5953e9cb41..8691ecac4d1cc 100644 --- a/x-pack/plugins/security_solution/public/resolver/mocks/resolver_tree.ts +++ b/x-pack/plugins/security_solution/public/resolver/mocks/resolver_tree.ts @@ -5,7 +5,8 @@ */ import { mockEndpointEvent } from './endpoint_event'; -import { ResolverTree, ResolverEvent, SafeResolverEvent } from '../../../common/endpoint/types'; +import { ResolverTree, SafeResolverEvent } from '../../../common/endpoint/types'; +import * as eventModel from '../../../common/endpoint/models/event'; export function mockTreeWith2AncestorsAndNoChildren({ originID, @@ -16,34 +17,42 @@ export function mockTreeWith2AncestorsAndNoChildren({ firstAncestorID: string; originID: string; }): ResolverTree { - const secondAncestor: ResolverEvent = mockEndpointEvent({ + const secondAncestor: SafeResolverEvent = mockEndpointEvent({ entityID: secondAncestorID, - name: 'a', - parentEntityId: 'none', + processName: 'a', + parentEntityID: 'none', timestamp: 0, }); - const firstAncestor: ResolverEvent = mockEndpointEvent({ + const firstAncestor: SafeResolverEvent = mockEndpointEvent({ entityID: firstAncestorID, - name: 'b', - parentEntityId: secondAncestorID, + processName: 'b', + parentEntityID: secondAncestorID, timestamp: 1, }); - const originEvent: ResolverEvent = mockEndpointEvent({ + const originEvent: SafeResolverEvent = mockEndpointEvent({ entityID: originID, - name: 'c', - parentEntityId: firstAncestorID, + processName: 'c', + parentEntityID: firstAncestorID, timestamp: 2, }); - return ({ + return { entityID: originID, children: { childNodes: [], + nextChild: null, }, ancestry: { - ancestors: [{ lifecycle: [secondAncestor] }, { lifecycle: [firstAncestor] }], + nextAncestor: null, + ancestors: [ + { entityID: secondAncestorID, lifecycle: [secondAncestor] }, + { entityID: firstAncestorID, lifecycle: [firstAncestor] }, + ], }, lifecycle: [originEvent], - } as unknown) as ResolverTree; + relatedEvents: { events: [], nextEvent: null }, + relatedAlerts: { alerts: [], nextAlert: null }, + stats: { events: { total: 2, byCategory: {} }, totalAlerts: 0 }, + }; } export function mockTreeWithAllProcessesTerminated({ @@ -55,44 +64,44 @@ export function mockTreeWithAllProcessesTerminated({ firstAncestorID: string; originID: string; }): ResolverTree { - const secondAncestor: ResolverEvent = mockEndpointEvent({ + const secondAncestor: SafeResolverEvent = mockEndpointEvent({ entityID: secondAncestorID, - name: 'a', - parentEntityId: 'none', + processName: 'a', + parentEntityID: 'none', timestamp: 0, }); - const firstAncestor: ResolverEvent = mockEndpointEvent({ + const firstAncestor: SafeResolverEvent = mockEndpointEvent({ entityID: firstAncestorID, - name: 'b', - parentEntityId: secondAncestorID, + processName: 'b', + parentEntityID: secondAncestorID, timestamp: 1, }); - const originEvent: ResolverEvent = mockEndpointEvent({ + const originEvent: SafeResolverEvent = mockEndpointEvent({ entityID: originID, - name: 'c', - parentEntityId: firstAncestorID, + processName: 'c', + parentEntityID: firstAncestorID, timestamp: 2, }); - const secondAncestorTermination: ResolverEvent = mockEndpointEvent({ + const secondAncestorTermination: SafeResolverEvent = mockEndpointEvent({ entityID: secondAncestorID, - name: 'a', - parentEntityId: 'none', + processName: 'a', + parentEntityID: 'none', timestamp: 0, - lifecycleType: 'end', + eventType: 'end', }); - const firstAncestorTermination: ResolverEvent = mockEndpointEvent({ + const firstAncestorTermination: SafeResolverEvent = mockEndpointEvent({ entityID: firstAncestorID, - name: 'b', - parentEntityId: secondAncestorID, + processName: 'b', + parentEntityID: secondAncestorID, timestamp: 1, - lifecycleType: 'end', + eventType: 'end', }); - const originEventTermination: ResolverEvent = mockEndpointEvent({ + const originEventTermination: SafeResolverEvent = mockEndpointEvent({ entityID: originID, - name: 'c', - parentEntityId: firstAncestorID, + processName: 'c', + parentEntityID: firstAncestorID, timestamp: 2, - lifecycleType: 'end', + eventType: 'end', }); return ({ entityID: originID, @@ -109,26 +118,10 @@ export function mockTreeWithAllProcessesTerminated({ } as unknown) as ResolverTree; } -/** - * A valid category for a related event. E.g. "registry", "network", "file" - */ -type RelatedEventCategory = string; -/** - * A valid type for a related event. E.g. "start", "end", "access" - */ -type RelatedEventType = string; - /** * Add/replace related event info (on origin node) for any mock ResolverTree - * - * @param treeToAddRelatedEventsTo the ResolverTree to modify - * @param relatedEventsToAddByCategoryAndType Iterable of `[category, type]` pairs describing related events. e.g. [['dns','info'],['registry','access']] */ -function withRelatedEventsOnOrigin( - treeToAddRelatedEventsTo: ResolverTree, - relatedEventsToAddByCategoryAndType: Iterable<[RelatedEventCategory, RelatedEventType]> -): ResolverTree { - const events: SafeResolverEvent[] = []; +function withRelatedEventsOnOrigin(tree: ResolverTree, events: SafeResolverEvent[]): ResolverTree { const byCategory: Record = {}; const stats = { totalAlerts: 0, @@ -137,29 +130,19 @@ function withRelatedEventsOnOrigin( byCategory, }, }; - for (const [category, type] of relatedEventsToAddByCategoryAndType) { - events.push({ - '@timestamp': 1, - event: { - kind: 'event', - type, - category, - id: 'xyz', - }, - process: { - entity_id: treeToAddRelatedEventsTo.entityID, - }, - }); + for (const event of events) { stats.events.total++; - stats.events.byCategory[category] = stats.events.byCategory[category] - ? stats.events.byCategory[category] + 1 - : 1; + for (const category of eventModel.eventCategory(event)) { + stats.events.byCategory[category] = stats.events.byCategory[category] + ? stats.events.byCategory[category] + 1 + : 1; + } } return { - ...treeToAddRelatedEventsTo, + ...tree, stats, relatedEvents: { - events: events as ResolverEvent[], + events, nextEvent: null, }, }; @@ -174,38 +157,46 @@ export function mockTreeWithNoAncestorsAnd2Children({ firstChildID: string; secondChildID: string; }): ResolverTree { - const origin: ResolverEvent = mockEndpointEvent({ + const origin: SafeResolverEvent = mockEndpointEvent({ pid: 0, entityID: originID, - name: 'c.ext', - parentEntityId: 'none', + processName: 'c.ext', + parentEntityID: 'none', timestamp: 0, }); - const firstChild: ResolverEvent = mockEndpointEvent({ + const firstChild: SafeResolverEvent = mockEndpointEvent({ pid: 1, entityID: firstChildID, - name: 'd', - parentEntityId: originID, + processName: 'd', + parentEntityID: originID, timestamp: 1, }); - const secondChild: ResolverEvent = mockEndpointEvent({ + const secondChild: SafeResolverEvent = mockEndpointEvent({ pid: 2, entityID: secondChildID, - name: 'e', - parentEntityId: originID, + processName: 'e', + parentEntityID: originID, timestamp: 2, }); - return ({ + return { entityID: originID, children: { - childNodes: [{ lifecycle: [firstChild] }, { lifecycle: [secondChild] }], + childNodes: [ + { entityID: firstChildID, lifecycle: [firstChild] }, + { entityID: secondChildID, lifecycle: [secondChild] }, + ], + nextChild: null, }, ancestry: { ancestors: [], + nextAncestor: null, }, lifecycle: [origin], - } as unknown) as ResolverTree; + relatedEvents: { events: [], nextEvent: null }, + relatedAlerts: { alerts: [], nextAlert: null }, + stats: { events: { total: 2, byCategory: {} }, totalAlerts: 0 }, + }; } /** @@ -222,52 +213,52 @@ export function mockTreeWith1AncestorAnd2ChildrenAndAllNodesHave2GraphableEvents firstChildID: string; secondChildID: string; }): ResolverTree { - const ancestor: ResolverEvent = mockEndpointEvent({ + const ancestor: SafeResolverEvent = mockEndpointEvent({ entityID: ancestorID, - name: ancestorID, + processName: ancestorID, timestamp: 1, - parentEntityId: undefined, + parentEntityID: undefined, }); - const ancestorClone: ResolverEvent = mockEndpointEvent({ + const ancestorClone: SafeResolverEvent = mockEndpointEvent({ entityID: ancestorID, - name: ancestorID, + processName: ancestorID, timestamp: 1, - parentEntityId: undefined, + parentEntityID: undefined, }); - const origin: ResolverEvent = mockEndpointEvent({ + const origin: SafeResolverEvent = mockEndpointEvent({ entityID: originID, - name: originID, - parentEntityId: ancestorID, + processName: originID, + parentEntityID: ancestorID, timestamp: 0, }); - const originClone: ResolverEvent = mockEndpointEvent({ + const originClone: SafeResolverEvent = mockEndpointEvent({ entityID: originID, - name: originID, - parentEntityId: ancestorID, + processName: originID, + parentEntityID: ancestorID, timestamp: 0, }); - const firstChild: ResolverEvent = mockEndpointEvent({ + const firstChild: SafeResolverEvent = mockEndpointEvent({ entityID: firstChildID, - name: firstChildID, - parentEntityId: originID, + processName: firstChildID, + parentEntityID: originID, timestamp: 1, }); - const firstChildClone: ResolverEvent = mockEndpointEvent({ + const firstChildClone: SafeResolverEvent = mockEndpointEvent({ entityID: firstChildID, - name: firstChildID, - parentEntityId: originID, + processName: firstChildID, + parentEntityID: originID, timestamp: 1, }); - const secondChild: ResolverEvent = mockEndpointEvent({ + const secondChild: SafeResolverEvent = mockEndpointEvent({ entityID: secondChildID, - name: secondChildID, - parentEntityId: originID, + processName: secondChildID, + parentEntityID: originID, timestamp: 2, }); - const secondChildClone: ResolverEvent = mockEndpointEvent({ + const secondChildClone: SafeResolverEvent = mockEndpointEvent({ entityID: secondChildID, - name: secondChildID, - parentEntityId: originID, + processName: secondChildID, + parentEntityID: originID, timestamp: 2, }); @@ -330,9 +321,22 @@ export function mockTreeWithNoAncestorsAndTwoChildrenAndRelatedEventsOnOrigin({ firstChildID, secondChildID, }); - const withRelatedEvents: Array<[string, string]> = [ - ['registry', 'access'], - ['registry', 'access'], + const parentEntityID = eventModel.parentEntityIDSafeVersion(baseTree.lifecycle[0]); + const relatedEvents = [ + mockEndpointEvent({ + entityID: originID, + parentEntityID, + eventID: 'first related event', + eventType: 'access', + eventCategory: 'registry', + }), + mockEndpointEvent({ + entityID: originID, + parentEntityID, + eventID: 'second related event', + eventType: 'access', + eventCategory: 'registry', + }), ]; - return withRelatedEventsOnOrigin(baseTree, withRelatedEvents); + return withRelatedEventsOnOrigin(baseTree, relatedEvents); } diff --git a/x-pack/plugins/security_solution/public/resolver/models/process_event.test.ts b/x-pack/plugins/security_solution/public/resolver/models/process_event.test.ts index 4d48b34fb2841..380b15cf9da4c 100644 --- a/x-pack/plugins/security_solution/public/resolver/models/process_event.test.ts +++ b/x-pack/plugins/security_solution/public/resolver/models/process_event.test.ts @@ -6,11 +6,7 @@ import { eventType, orderByTime, userInfoForProcess } from './process_event'; import { mockProcessEvent } from './process_event_test_helpers'; -import { - LegacyEndpointEvent, - ResolverEvent, - SafeResolverEvent, -} from '../../../common/endpoint/types'; +import { LegacyEndpointEvent, SafeResolverEvent } from '../../../common/endpoint/types'; describe('process event', () => { describe('eventType', () => { @@ -45,7 +41,7 @@ describe('process event', () => { }); }); describe('orderByTime', () => { - let mock: (time: number, eventID: string) => ResolverEvent; + let mock: (time: number, eventID: string) => SafeResolverEvent; let events: SafeResolverEvent[]; beforeEach(() => { mock = (time, eventID) => { @@ -54,20 +50,20 @@ describe('process event', () => { event: { id: eventID, }, - } as ResolverEvent; + }; }; // 2 events each for numbers -1, 0, 1, and NaN // each event has a unique id, a through h // order is arbitrary events = [ - mock(-1, 'a') as SafeResolverEvent, - mock(0, 'c') as SafeResolverEvent, - mock(1, 'e') as SafeResolverEvent, - mock(NaN, 'g') as SafeResolverEvent, - mock(-1, 'b') as SafeResolverEvent, - mock(0, 'd') as SafeResolverEvent, - mock(1, 'f') as SafeResolverEvent, - mock(NaN, 'h') as SafeResolverEvent, + mock(-1, 'a'), + mock(0, 'c'), + mock(1, 'e'), + mock(NaN, 'g'), + mock(-1, 'b'), + mock(0, 'd'), + mock(1, 'f'), + mock(NaN, 'h'), ]; }); it('sorts events as expected', () => { diff --git a/x-pack/plugins/security_solution/public/resolver/models/process_event.ts b/x-pack/plugins/security_solution/public/resolver/models/process_event.ts index ea588731a55c8..1510fc7f9f365 100644 --- a/x-pack/plugins/security_solution/public/resolver/models/process_event.ts +++ b/x-pack/plugins/security_solution/public/resolver/models/process_event.ts @@ -4,7 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as event from '../../../common/endpoint/models/event'; +import { firstNonNullValue } from '../../../common/endpoint/models/ecs_safety_helpers'; + +import * as eventModel from '../../../common/endpoint/models/event'; import { ResolverEvent, SafeResolverEvent } from '../../../common/endpoint/types'; import { ResolverProcessType } from '../types'; @@ -12,19 +14,11 @@ import { ResolverProcessType } from '../types'; * Returns true if the process's eventType is either 'processCreated' or 'processRan'. * Resolver will only render 'graphable' process events. */ -export function isGraphableProcess(passedEvent: ResolverEvent) { +export function isGraphableProcess(passedEvent: SafeResolverEvent) { return eventType(passedEvent) === 'processCreated' || eventType(passedEvent) === 'processRan'; } -function isValue(field: string | string[], value: string) { - if (field instanceof Array) { - return field.length === 1 && field[0] === value; - } else { - return field === value; - } -} - -export function isTerminatedProcess(passedEvent: ResolverEvent) { +export function isTerminatedProcess(passedEvent: SafeResolverEvent) { return eventType(passedEvent) === 'processTerminated'; } @@ -33,7 +27,7 @@ export function isTerminatedProcess(passedEvent: ResolverEvent) { * may return NaN if the timestamp wasn't present or was invalid. */ export function datetime(passedEvent: SafeResolverEvent): number | null { - const timestamp = event.timestampSafeVersion(passedEvent); + const timestamp = eventModel.timestampSafeVersion(passedEvent); const time = timestamp === undefined ? 0 : new Date(timestamp).getTime(); @@ -44,8 +38,8 @@ export function datetime(passedEvent: SafeResolverEvent): number | null { /** * Returns a custom event type for a process event based on the event's metadata. */ -export function eventType(passedEvent: ResolverEvent): ResolverProcessType { - if (event.isLegacyEvent(passedEvent)) { +export function eventType(passedEvent: SafeResolverEvent): ResolverProcessType { + if (eventModel.isLegacyEventSafeVersion(passedEvent)) { const { endgame: { event_type_full: type, event_subtype_full: subType }, } = passedEvent; @@ -64,20 +58,20 @@ export function eventType(passedEvent: ResolverEvent): ResolverProcessType { return 'processCausedAlert'; } } else { - const { - event: { type, category, kind }, - } = passedEvent; - if (isValue(category, 'process')) { - if (isValue(type, 'start') || isValue(type, 'change') || isValue(type, 'creation')) { + const type = new Set(eventModel.eventType(passedEvent)); + const category = new Set(eventModel.eventCategory(passedEvent)); + const kind = new Set(eventModel.eventKind(passedEvent)); + if (category.has('process')) { + if (type.has('start') || type.has('change') || type.has('creation')) { return 'processCreated'; - } else if (isValue(type, 'info')) { + } else if (type.has('info')) { return 'processRan'; - } else if (isValue(type, 'end')) { + } else if (type.has('end')) { return 'processTerminated'; } else { return 'unknownProcessEvent'; } - } else if (kind === 'alert') { + } else if (kind.has('alert')) { return 'processCausedAlert'; } } @@ -88,7 +82,7 @@ export function eventType(passedEvent: ResolverEvent): ResolverProcessType { * Returns the process event's PID */ export function uniquePidForProcess(passedEvent: ResolverEvent): string { - if (event.isLegacyEvent(passedEvent)) { + if (eventModel.isLegacyEvent(passedEvent)) { return String(passedEvent.endgame.unique_pid); } else { return passedEvent.process.entity_id; @@ -98,45 +92,32 @@ export function uniquePidForProcess(passedEvent: ResolverEvent): string { /** * Returns the PID for the process on the host */ -export function processPid(passedEvent: ResolverEvent): number | undefined { - if (event.isLegacyEvent(passedEvent)) { - return passedEvent.endgame.pid; - } else { - return passedEvent.process.pid; - } +export function processPID(event: SafeResolverEvent): number | undefined { + return firstNonNullValue( + eventModel.isLegacyEventSafeVersion(event) ? event.endgame.pid : event.process?.pid + ); } /** * Returns the process event's parent PID */ export function uniqueParentPidForProcess(passedEvent: ResolverEvent): string | undefined { - if (event.isLegacyEvent(passedEvent)) { + if (eventModel.isLegacyEvent(passedEvent)) { return String(passedEvent.endgame.unique_ppid); } else { return passedEvent.process.parent?.entity_id; } } -/** - * Returns the process event's parent PID - */ -export function processParentPid(passedEvent: ResolverEvent): number | undefined { - if (event.isLegacyEvent(passedEvent)) { - return passedEvent.endgame.ppid; - } else { - return passedEvent.process.parent?.pid; - } -} - /** * Returns the process event's path on its host */ -export function processPath(passedEvent: ResolverEvent): string | undefined { - if (event.isLegacyEvent(passedEvent)) { - return passedEvent.endgame.process_path; - } else { - return passedEvent.process.executable; - } +export function processPath(passedEvent: SafeResolverEvent): string | undefined { + return firstNonNullValue( + eventModel.isLegacyEventSafeVersion(passedEvent) + ? passedEvent.endgame.process_path + : passedEvent.process?.executable + ); } /** @@ -148,19 +129,6 @@ export function userInfoForProcess( return passedEvent.user; } -/** - * Returns the MD5 hash for the `passedEvent` parameter, or undefined if it can't be located - * @param {ResolverEvent} passedEvent The `ResolverEvent` to get the MD5 value for - * @returns {string | undefined} The MD5 string for the event - */ -export function md5HashForProcess(passedEvent: ResolverEvent): string | undefined { - if (event.isLegacyEvent(passedEvent)) { - // There is not currently a key for this on Legacy event types - return undefined; - } - return passedEvent?.process?.hash?.md5; -} - /** * Returns the command line path and arguments used to run the `passedEvent` if any * @@ -168,7 +136,7 @@ export function md5HashForProcess(passedEvent: ResolverEvent): string | undefine * @returns {string | undefined} The arguments (including the path) used to run the process */ export function argsForProcess(passedEvent: ResolverEvent): string | undefined { - if (event.isLegacyEvent(passedEvent)) { + if (eventModel.isLegacyEvent(passedEvent)) { // There is not currently a key for this on Legacy event types return undefined; } @@ -184,8 +152,8 @@ export function orderByTime(first: SafeResolverEvent, second: SafeResolverEvent) if (firstDatetime === secondDatetime) { // break ties using an arbitrary (stable) comparison of `eventId` (which should be unique) - return String(event.eventIDSafeVersion(first)).localeCompare( - String(event.eventIDSafeVersion(second)) + return String(eventModel.eventIDSafeVersion(first)).localeCompare( + String(eventModel.eventIDSafeVersion(second)) ); } else if (firstDatetime === null || secondDatetime === null) { // sort `null`'s as higher than numbers diff --git a/x-pack/plugins/security_solution/public/resolver/models/resolver_tree.ts b/x-pack/plugins/security_solution/public/resolver/models/resolver_tree.ts index 446e371832d38..775b88246b61f 100644 --- a/x-pack/plugins/security_solution/public/resolver/models/resolver_tree.ts +++ b/x-pack/plugins/security_solution/public/resolver/models/resolver_tree.ts @@ -6,12 +6,12 @@ import { ResolverTree, - ResolverEvent, ResolverNodeStats, ResolverLifecycleNode, ResolverChildNode, + SafeResolverEvent, } from '../../../common/endpoint/types'; -import { uniquePidForProcess } from './process_event'; +import * as eventModel from '../../../common/endpoint/models/event'; /** * ResolverTree is a type returned by the server. @@ -29,7 +29,7 @@ function lifecycleNodes(tree: ResolverTree): ResolverLifecycleNode[] { * All the process events */ export function lifecycleEvents(tree: ResolverTree) { - const events: ResolverEvent[] = [...tree.lifecycle]; + const events: SafeResolverEvent[] = [...tree.lifecycle]; for (const { lifecycle } of tree.children.childNodes) { events.push(...lifecycle); } @@ -66,7 +66,7 @@ export function mock({ /** * Events represented by the ResolverTree. */ - events: ResolverEvent[]; + events: SafeResolverEvent[]; children?: ResolverChildNode[]; /** * Optionally provide cursors for the 'children' and 'ancestry' edges. @@ -77,8 +77,12 @@ export function mock({ return null; } const first = events[0]; + const entityID = eventModel.entityIDSafeVersion(first); + if (!entityID) { + throw new Error('first mock event must include an entityID.'); + } return { - entityID: uniquePidForProcess(first), + entityID, // Required children: { childNodes: children, diff --git a/x-pack/plugins/security_solution/public/resolver/store/actions.ts b/x-pack/plugins/security_solution/public/resolver/store/actions.ts index 6a02d5b76bc4c..3348c962efdea 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/actions.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/actions.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { CameraAction } from './camera'; -import { ResolverEvent } from '../../../common/endpoint/types'; +import { SafeResolverEvent } from '../../../common/endpoint/types'; import { DataAction } from './data/action'; /** @@ -16,25 +16,7 @@ interface UserBroughtProcessIntoView { /** * Used to identify the process node that should be brought into view. */ - readonly process: ResolverEvent; - /** - * The time (since epoch in milliseconds) when the action was dispatched. - */ - readonly time: number; - }; -} - -/** - * When an examination of query params in the UI indicates that state needs to - * be updated to reflect the new selection - */ -interface AppDetectedNewIdFromQueryParams { - readonly type: 'appDetectedNewIdFromQueryParams'; - readonly payload: { - /** - * Used to identify the process the process that should be synced with state. - */ - readonly process: ResolverEvent; + readonly process: SafeResolverEvent; /** * The time (since epoch in milliseconds) when the action was dispatched. */ @@ -51,15 +33,6 @@ interface UserRequestedRelatedEventData { readonly payload: string; } -/** - * The action dispatched when the app requests related event data for one - * subject (whose entity_id should be included as `payload`) - */ -interface AppDetectedMissingEventData { - readonly type: 'appDetectedMissingEventData'; - readonly payload: string; -} - /** * When the user switches the "active descendant" of the Resolver. * The "active descendant" (from the point of view of the parent element) @@ -127,6 +100,4 @@ export type ResolverAction = | UserBroughtProcessIntoView | UserFocusedOnResolverNode | UserSelectedResolverNode - | UserRequestedRelatedEventData - | AppDetectedNewIdFromQueryParams - | AppDetectedMissingEventData; + | UserRequestedRelatedEventData; diff --git a/x-pack/plugins/security_solution/public/resolver/store/data/action.ts b/x-pack/plugins/security_solution/public/resolver/store/data/action.ts index 59d1494ae8c27..0cb1cd1cec771 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/data/action.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/data/action.ts @@ -45,14 +45,6 @@ interface AppAbortedResolverDataRequest { readonly payload: TreeFetcherParameters; } -/** - * Will occur when a request for related event data is unsuccessful. - */ -interface ServerFailedToReturnRelatedEventData { - readonly type: 'serverFailedToReturnRelatedEventData'; - readonly payload: string; -} - /** * When related events are returned from the server */ @@ -64,7 +56,6 @@ interface ServerReturnedRelatedEventData { export type DataAction = | ServerReturnedResolverData | ServerFailedToReturnResolverData - | ServerFailedToReturnRelatedEventData | ServerReturnedRelatedEventData | AppRequestedResolverData | AppAbortedResolverDataRequest; diff --git a/x-pack/plugins/security_solution/public/resolver/store/data/reducer.test.ts b/x-pack/plugins/security_solution/public/resolver/store/data/reducer.test.ts index 1e2de06ea4af5..5714345de0431 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/data/reducer.test.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/data/reducer.test.ts @@ -10,8 +10,7 @@ import { dataReducer } from './reducer'; import * as selectors from './selectors'; import { DataState } from '../../types'; import { DataAction } from './action'; -import { ResolverChildNode, ResolverEvent, ResolverTree } from '../../../../common/endpoint/types'; -import * as eventModel from '../../../../common/endpoint/models/event'; +import { ResolverChildNode, ResolverTree } from '../../../../common/endpoint/types'; import { values } from '../../../../common/endpoint/models/ecs_safety_helpers'; import { mockTreeFetcherParameters } from '../../mocks/tree_fetcher_parameters'; @@ -43,7 +42,7 @@ describe('Resolver Data Middleware', () => { const tree = mockResolverTree({ // Casting here because the generator returns the SafeResolverEvent type which isn't yet compatible with // a lot of the frontend functions. So casting it back to the unsafe type for now. - events: baseTree.allEvents as ResolverEvent[], + events: baseTree.allEvents, cursors: { childrenNextChild: 'aValidChildCursor', ancestryNextAncestor: 'aValidAncestorCursor', @@ -61,9 +60,6 @@ describe('Resolver Data Middleware', () => { describe('when data was received with stats mocked for the first child node', () => { let firstChildNodeInTree: TreeNode; - let eventStatsForFirstChildNode: { total: number; byCategory: Record }; - let categoryToOverCount: string; - let aggregateCategoryTotalForFirstChildNode: number; let tree: ResolverTree; /** @@ -73,13 +69,7 @@ describe('Resolver Data Middleware', () => { */ beforeEach(() => { - ({ - tree, - firstChildNodeInTree, - eventStatsForFirstChildNode, - categoryToOverCount, - aggregateCategoryTotalForFirstChildNode, - } = mockedTree()); + ({ tree, firstChildNodeInTree } = mockedTree()); if (tree) { dispatchTree(tree); } @@ -94,7 +84,7 @@ describe('Resolver Data Middleware', () => { entityID: firstChildNodeInTree.id, // Casting here because the generator returns the SafeResolverEvent type which isn't yet compatible with // a lot of the frontend functions. So casting it back to the unsafe type for now. - events: firstChildNodeInTree.relatedEvents as ResolverEvent[], + events: firstChildNodeInTree.relatedEvents, nextEvent: null, }, }; @@ -108,121 +98,6 @@ describe('Resolver Data Middleware', () => { expect(selectedEventsForFirstChildNode).toBe(firstChildNodeInTree.relatedEvents); }); - it('should indicate the correct related event count for each category', () => { - const selectedRelatedInfo = selectors.relatedEventInfoByEntityId(store.getState()); - const displayCountsForCategory = selectedRelatedInfo(firstChildNodeInTree.id) - ?.numberActuallyDisplayedForCategory!; - - const eventCategoriesForNode: string[] = Object.keys( - eventStatsForFirstChildNode.byCategory - ); - - for (const eventCategory of eventCategoriesForNode) { - expect(`${eventCategory}:${displayCountsForCategory(eventCategory)}`).toBe( - `${eventCategory}:${eventStatsForFirstChildNode.byCategory[eventCategory]}` - ); - } - }); - /** - * The general approach reflected here is to _avoid_ showing a limit warning - even if we hit - * the overall related event limit - as long as the number in our category matches what the stats - * say we have. E.g. If the stats say you have 20 dns events, and we receive 20 dns events, we - * don't need to display a limit warning for that, even if we hit some overall event limit of e.g. 100 - * while we were fetching the 20. - */ - it('should not indicate the limit has been exceeded because the number of related events received for the category is greater or equal to the stats count', () => { - const selectedRelatedInfo = selectors.relatedEventInfoByEntityId(store.getState()); - const shouldShowLimit = selectedRelatedInfo(firstChildNodeInTree.id) - ?.shouldShowLimitForCategory!; - for (const typeCounted of Object.keys(eventStatsForFirstChildNode.byCategory)) { - expect(shouldShowLimit(typeCounted)).toBe(false); - } - }); - it('should not indicate that there are any related events missing because the number of related events received for the category is greater or equal to the stats count', () => { - const selectedRelatedInfo = selectors.relatedEventInfoByEntityId(store.getState()); - const notDisplayed = selectedRelatedInfo(firstChildNodeInTree.id) - ?.numberNotDisplayedForCategory!; - for (const typeCounted of Object.keys(eventStatsForFirstChildNode.byCategory)) { - expect(notDisplayed(typeCounted)).toBe(0); - } - }); - it('should return an overall correct count for the number of related events', () => { - const aggregateTotalByEntityId = selectors.relatedEventAggregateTotalByEntityId( - store.getState() - ); - const countForId = aggregateTotalByEntityId(firstChildNodeInTree.id); - expect(countForId).toBe(aggregateCategoryTotalForFirstChildNode); - }); - }); - describe('when data was received and stats show more related events than the API can provide', () => { - beforeEach(() => { - // Add 1 to the stats for an event category so that the selectors think we are missing data. - // This mutates `tree`, and then we re-dispatch it - eventStatsForFirstChildNode.byCategory[categoryToOverCount] = - eventStatsForFirstChildNode.byCategory[categoryToOverCount] + 1; - - if (tree) { - dispatchTree(tree); - const relatedAction: DataAction = { - type: 'serverReturnedRelatedEventData', - payload: { - entityID: firstChildNodeInTree.id, - // Casting here because the generator returns the SafeResolverEvent type which isn't yet compatible with - // a lot of the frontend functions. So casting it back to the unsafe type for now. - events: firstChildNodeInTree.relatedEvents as ResolverEvent[], - nextEvent: 'aValidNextEventCursor', - }, - }; - store.dispatch(relatedAction); - } - }); - it('should have the correct related events', () => { - const selectedEventsByEntityId = selectors.relatedEventsByEntityId(store.getState()); - const selectedEventsForFirstChildNode = selectedEventsByEntityId.get( - firstChildNodeInTree.id - )!.events; - - expect(selectedEventsForFirstChildNode).toBe(firstChildNodeInTree.relatedEvents); - }); - it('should return related events for the category equal to the number of events of that type provided', () => { - const relatedEventsByCategory = selectors.relatedEventsByCategory(store.getState()); - const relatedEventsForOvercountedCategory = relatedEventsByCategory( - firstChildNodeInTree.id - )(categoryToOverCount); - expect(relatedEventsForOvercountedCategory.length).toBe( - eventStatsForFirstChildNode.byCategory[categoryToOverCount] - 1 - ); - }); - it('should return the correct related event detail metadata for a given related event', () => { - const relatedEventsByCategory = selectors.relatedEventsByCategory(store.getState()); - const someRelatedEventForTheFirstChild = relatedEventsByCategory(firstChildNodeInTree.id)( - categoryToOverCount - )[0]; - const relatedEventID = eventModel.eventId(someRelatedEventForTheFirstChild)!; - const relatedDisplayInfo = selectors.relatedEventDisplayInfoByEntityAndSelfID( - store.getState() - )(firstChildNodeInTree.id, relatedEventID); - const [, countOfSameType, , sectionData] = relatedDisplayInfo; - const hostEntries = sectionData.filter((section) => { - return section.sectionTitle === 'host'; - })[0].entries; - expect(hostEntries).toContainEqual({ title: 'os.platform', description: 'Windows' }); - expect(countOfSameType).toBe( - eventStatsForFirstChildNode.byCategory[categoryToOverCount] - 1 - ); - }); - it('should indicate the limit has been exceeded because the number of related events received for the category is less than what the stats count said it would be', () => { - const selectedRelatedInfo = selectors.relatedEventInfoByEntityId(store.getState()); - const shouldShowLimit = selectedRelatedInfo(firstChildNodeInTree.id) - ?.shouldShowLimitForCategory!; - expect(shouldShowLimit(categoryToOverCount)).toBe(true); - }); - it('should indicate that there are related events missing because the number of related events received for the category is less than what the stats count said it would be', () => { - const selectedRelatedInfo = selectors.relatedEventInfoByEntityId(store.getState()); - const notDisplayed = selectedRelatedInfo(firstChildNodeInTree.id) - ?.numberNotDisplayedForCategory!; - expect(notDisplayed(categoryToOverCount)).toBe(1); - }); }); }); }); @@ -241,7 +116,7 @@ function mockedTree() { const tree = mockResolverTree({ // Casting here because the generator returns the SafeResolverEvent type which isn't yet compatible with // a lot of the frontend functions. So casting it back to the unsafe type for now. - events: baseTree.allEvents as ResolverEvent[], + events: baseTree.allEvents, /** * Calculate children from the ResolverTree response using the children of the `Tree` we generated using the Resolver data generator code. * Compile (and attach) stats to the first child node. @@ -255,7 +130,7 @@ function mockedTree() { const childNode: Partial = {}; // Casting here because the generator returns the SafeResolverEvent type which isn't yet compatible with // a lot of the frontend functions. So casting it back to the unsafe type for now. - childNode.lifecycle = node.lifecycle as ResolverEvent[]; + childNode.lifecycle = node.lifecycle; // `TreeNode` has `id` which is the same as `entityID`. // The `ResolverChildNode` calls the entityID as `entityID`. @@ -281,8 +156,6 @@ function mockedTree() { return { tree: tree!, firstChildNodeInTree, - eventStatsForFirstChildNode: statsResults.eventStats, - aggregateCategoryTotalForFirstChildNode: statsResults.aggregateCategoryTotal, categoryToOverCount: statsResults.firstCategory, }; } @@ -309,7 +182,6 @@ function compileStatsForChild( }; /** The category of the first event. */ firstCategory: string; - aggregateCategoryTotal: number; } { const totalRelatedEvents = node.relatedEvents.length; // For the purposes of testing, we pick one category to fake an extra event for @@ -317,12 +189,6 @@ function compileStatsForChild( let firstCategory: string | undefined; - // This is the "aggregate total" which is displayed to users as the total count - // of related events for the node. It is tallied by incrementing for every discrete - // event.category in an event.category array (or just 1 for a plain string). E.g. two events - // categories 'file' and ['dns','network'] would have an `aggregate total` of 3. - let aggregateCategoryTotal: number = 0; - const compiledStats = node.relatedEvents.reduce( (counts: Record, relatedEvent) => { // get an array of categories regardless of whether category is a string or string[] @@ -336,7 +202,6 @@ function compileStatsForChild( // Increment the count of events with this category counts[category] = counts[category] ? counts[category] + 1 : 1; - aggregateCategoryTotal++; } return counts; }, @@ -354,6 +219,5 @@ function compileStatsForChild( byCategory: compiledStats, }, firstCategory, - aggregateCategoryTotal, }; } diff --git a/x-pack/plugins/security_solution/public/resolver/store/data/reducer.ts b/x-pack/plugins/security_solution/public/resolver/store/data/reducer.ts index c8df95aaee6f4..1819407a19516 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/data/reducer.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/data/reducer.ts @@ -11,9 +11,7 @@ import * as treeFetcherParameters from '../../models/tree_fetcher_parameters'; const initialState: DataState = { relatedEvents: new Map(), - relatedEventsReady: new Map(), resolverComponentInstanceID: undefined, - tree: {}, }; export const dataReducer: Reducer = (state = initialState, action) => { @@ -44,7 +42,7 @@ export const dataReducer: Reducer = (state = initialS }; return nextState; } else if (action.type === 'appAbortedResolverDataRequest') { - if (treeFetcherParameters.equal(action.payload, state.tree.pendingRequestParameters)) { + if (treeFetcherParameters.equal(action.payload, state.tree?.pendingRequestParameters)) { // the request we were awaiting was aborted const nextState: DataState = { ...state, @@ -81,7 +79,7 @@ export const dataReducer: Reducer = (state = initialS return nextState; } else if (action.type === 'serverFailedToReturnResolverData') { /** Only handle this if we are expecting a response */ - if (state.tree.pendingRequestParameters !== undefined) { + if (state.tree?.pendingRequestParameters !== undefined) { const nextState: DataState = { ...state, tree: { @@ -97,19 +95,9 @@ export const dataReducer: Reducer = (state = initialS } else { return state; } - } else if ( - action.type === 'userRequestedRelatedEventData' || - action.type === 'appDetectedMissingEventData' - ) { - const nextState: DataState = { - ...state, - relatedEventsReady: new Map([...state.relatedEventsReady, [action.payload, false]]), - }; - return nextState; } else if (action.type === 'serverReturnedRelatedEventData') { const nextState: DataState = { ...state, - relatedEventsReady: new Map([...state.relatedEventsReady, [action.payload.entityID, true]]), relatedEvents: new Map([...state.relatedEvents, [action.payload.entityID, action.payload]]), }; return nextState; diff --git a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.test.ts b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.test.ts index 539325faffdf0..d9717b52d9ce1 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.test.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.test.ts @@ -16,7 +16,7 @@ import { mockTreeWithAllProcessesTerminated, mockTreeWithNoProcessEvents, } from '../../mocks/resolver_tree'; -import { uniquePidForProcess } from '../../models/process_event'; +import * as eventModel from '../../../../common/endpoint/models/event'; import { EndpointEvent } from '../../../../common/endpoint/types'; import { mockTreeFetcherParameters } from '../../mocks/tree_fetcher_parameters'; @@ -411,7 +411,7 @@ describe('data state', () => { expect(graphables.length).toBe(3); for (const event of graphables) { expect(() => { - selectors.ariaFlowtoCandidate(state())(uniquePidForProcess(event)); + selectors.ariaFlowtoCandidate(state())(eventModel.entityIDSafeVersion(event)!); }).not.toThrow(); } }); diff --git a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts index d714ddb181470..c7829fa8a69b3 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts @@ -14,46 +14,36 @@ import { IndexedProcessNode, AABB, VisibleEntites, - SectionData, TreeFetcherParameters, } from '../../types'; -import { - isGraphableProcess, - isTerminatedProcess, - uniquePidForProcess, - uniqueParentPidForProcess, -} from '../../models/process_event'; +import { isGraphableProcess, isTerminatedProcess } from '../../models/process_event'; import * as indexedProcessTreeModel from '../../models/indexed_process_tree'; +import * as eventModel from '../../../../common/endpoint/models/event'; import { - ResolverEvent, ResolverTree, ResolverNodeStats, ResolverRelatedEvents, SafeResolverEvent, - EndpointEvent, - LegacyEndpointEvent, } from '../../../../common/endpoint/types'; import * as resolverTreeModel from '../../models/resolver_tree'; import * as treeFetcherParametersModel from '../../models/tree_fetcher_parameters'; import * as isometricTaxiLayoutModel from '../../models/indexed_process_tree/isometric_taxi_layout'; -import * as eventModel from '../../../../common/endpoint/models/event'; import * as vector2 from '../../models/vector2'; -import { formatDate } from '../../view/panels/panel_content_utilities'; /** * If there is currently a request. */ export function isTreeLoading(state: DataState): boolean { - return state.tree.pendingRequestParameters !== undefined; + return state.tree?.pendingRequestParameters !== undefined; } /** * If a request was made and it threw an error or returned a failure response code. */ export function hadErrorLoadingTree(state: DataState): boolean { - if (state.tree.lastResponse) { - return !state.tree.lastResponse.successful; + if (state.tree?.lastResponse) { + return !state.tree?.lastResponse.successful; } return false; } @@ -70,7 +60,7 @@ export function resolverComponentInstanceID(state: DataState): string { * we're currently interested in. */ const resolverTreeResponse = (state: DataState): ResolverTree | undefined => { - return state.tree.lastResponse?.successful ? state.tree.lastResponse.result : undefined; + return state.tree?.lastResponse?.successful ? state.tree?.lastResponse.result : undefined; }; /** @@ -102,7 +92,7 @@ export const terminatedProcesses = createSelector(resolverTreeResponse, function .lifecycleEvents(tree) .filter(isTerminatedProcess) .map((terminatedEvent) => { - return uniquePidForProcess(terminatedEvent); + return eventModel.entityIDSafeVersion(terminatedEvent); }) ); }); @@ -115,8 +105,8 @@ export const isProcessTerminated = createSelector(terminatedProcesses, function terminatedProcesses /* eslint-enable no-shadow */ ) { - return (entityId: string) => { - return terminatedProcesses.has(entityId); + return (entityID: string) => { + return terminatedProcesses.has(entityID); }; }); @@ -125,12 +115,14 @@ export const isProcessTerminated = createSelector(terminatedProcesses, function */ export const graphableProcesses = createSelector(resolverTreeResponse, function (tree?) { // Keep track of the last process event (in array order) for each entity ID - const events: Map = new Map(); + const events: Map = new Map(); if (tree) { for (const event of resolverTreeModel.lifecycleEvents(tree)) { if (isGraphableProcess(event)) { - const entityID = uniquePidForProcess(event); - events.set(entityID, event); + const entityID = eventModel.entityIDSafeVersion(event); + if (entityID !== undefined) { + events.set(entityID, event); + } } } return [...events.values()]; @@ -147,7 +139,7 @@ export const tree = createSelector(graphableProcesses, function indexedTree( graphableProcesses /* eslint-enable no-shadow */ ) { - return indexedProcessTreeModel.factory(graphableProcesses as SafeResolverEvent[]); + return indexedProcessTreeModel.factory(graphableProcesses); }); /** @@ -169,24 +161,18 @@ export const relatedEventsStats: ( ); /** - * This returns the "aggregate total" for related events, tallied as the sum - * of their individual `event.category`s. E.g. a [DNS, Network] would count as two - * towards the aggregate total. + * The total number of events related to a node. */ -export const relatedEventAggregateTotalByEntityId: ( +export const relatedEventTotalCount: ( state: DataState -) => (entityId: string) => number = createSelector(relatedEventsStats, (relatedStats) => { - return (entityId) => { - const statsForEntity = relatedStats(entityId); - if (statsForEntity === undefined) { - return 0; - } - return Object.values(statsForEntity?.events?.byCategory || {}).reduce( - (sum, val) => sum + val, - 0 - ); - }; -}); +) => (entityID: string) => number | undefined = createSelector( + relatedEventsStats, + (relatedStats) => { + return (entityID) => { + return relatedStats(entityID)?.events?.total; + }; + } +); /** * returns a map of entity_ids to related event data. @@ -197,98 +183,36 @@ export function relatedEventsByEntityId(data: DataState): Map
` entries - * @deprecated + * Get an event (from memory) by its `event.id`. + * @deprecated Use the API to find events by ID */ -const objectToDescriptionListEntries = function* ( - obj: object, - prefix = '' -): Generator<{ title: string; description: string }> { - const nextPrefix = prefix.length ? `${prefix}.` : ''; - for (const [metaKey, metaValue] of Object.entries(obj)) { - if (typeof metaValue === 'number' || typeof metaValue === 'string') { - yield { title: nextPrefix + metaKey, description: `${metaValue}` }; - } else if (metaValue instanceof Array) { - yield { - title: nextPrefix + metaKey, - description: metaValue - .filter((arrayEntry) => { - return typeof arrayEntry === 'number' || typeof arrayEntry === 'string'; - }) - .join(','), - }; - } else if (typeof metaValue === 'object') { - yield* objectToDescriptionListEntries(metaValue, nextPrefix + metaKey); +export const eventByID = createSelector(relatedEventsByEntityId, (relatedEvents) => { + // A map of nodeID to a map of eventID to events. Lazily populated. + const memo = new Map>(); + return ({ eventID, nodeID }: { eventID: string; nodeID: string }) => { + // We keep related events in a map by their nodeID. + const eventsWrapper = relatedEvents.get(nodeID); + if (!eventsWrapper) { + return undefined; } - } -}; - -/** - * Returns a function that returns the information needed to display related event details based on - * the related event's entityID and its own ID. - * @deprecated - */ -export const relatedEventDisplayInfoByEntityAndSelfID: ( - state: DataState -) => ( - entityId: string, - relatedEventId: string | number -) => [ - EndpointEvent | LegacyEndpointEvent | undefined, - number, - string | undefined, - SectionData, - string -] = createSelector(relatedEventsByEntityId, function relatedEventDetails( - /* eslint-disable no-shadow */ - relatedEventsByEntityId - /* eslint-enable no-shadow */ -) { - return defaultMemoize((entityId: string, relatedEventId: string | number) => { - const relatedEventsForThisProcess = relatedEventsByEntityId.get(entityId); - if (!relatedEventsForThisProcess) { - return [undefined, 0, undefined, [], '']; - } - const specificEvent = relatedEventsForThisProcess.events.find( - (evt) => eventModel.eventId(evt) === relatedEventId - ); - // For breadcrumbs: - const specificCategory = specificEvent && eventModel.primaryEventCategory(specificEvent); - const countOfCategory = relatedEventsForThisProcess.events.reduce((sumtotal, evt) => { - return eventModel.primaryEventCategory(evt) === specificCategory ? sumtotal + 1 : sumtotal; - }, 0); - // Assuming these details (agent, ecs, process) aren't as helpful, can revisit - const { agent, ecs, process, ...relevantData } = specificEvent as SafeResolverEvent & { - // Type this with various unknown keys so that ts will let us delete those keys - ecs: unknown; - process: unknown; - }; - - let displayDate = ''; - const sectionData: SectionData = Object.entries(relevantData) - .map(([sectionTitle, val]) => { - if (sectionTitle === '@timestamp') { - displayDate = formatDate(val); - return { sectionTitle: '', entries: [] }; + // When an event from a nodeID is requested, build a map for all events related to that node. + if (!memo.has(nodeID)) { + const map = new Map(); + for (const event of eventsWrapper.events) { + const id = eventModel.eventIDSafeVersion(event); + if (id !== undefined) { + map.set(id, event); } - if (typeof val !== 'object') { - return { sectionTitle, entries: [{ title: sectionTitle, description: `${val}` }] }; - } - return { sectionTitle, entries: [...objectToDescriptionListEntries(val)] }; - }) - .filter((v) => v.sectionTitle !== '' && v.entries.length); - - return [specificEvent, countOfCategory, specificCategory, sectionData, displayDate]; - }); + } + memo.set(nodeID, map); + } + const eventMap = memo.get(nodeID); + if (!eventMap) { + // This shouldn't be possible. + return undefined; + } + return eventMap.get(eventID); + }; }); /** @@ -298,44 +222,65 @@ export const relatedEventDisplayInfoByEntityAndSelfID: ( */ export const relatedEventsByCategory: ( state: DataState -) => (entityID: string) => (ecsCategory: string) => ResolverEvent[] = createSelector( +) => (node: string, eventCategory: string) => SafeResolverEvent[] = createSelector( relatedEventsByEntityId, function ( /* eslint-disable no-shadow */ relatedEventsByEntityId /* eslint-enable no-shadow */ ) { - return defaultMemoize((entityId: string) => { - return defaultMemoize((ecsCategory: string) => { - const relatedById = relatedEventsByEntityId.get(entityId); - // With no related events, we can't return related by category - if (!relatedById) { - return []; + // A map of nodeID -> event category -> SafeResolverEvent[] + const nodeMap: Map> = new Map(); + for (const [nodeID, events] of relatedEventsByEntityId) { + // A map of eventCategory -> SafeResolverEvent[] + let categoryMap = nodeMap.get(nodeID); + if (!categoryMap) { + categoryMap = new Map(); + nodeMap.set(nodeID, categoryMap); + } + + for (const event of events.events) { + for (const category of eventModel.eventCategory(event)) { + let eventsInCategory = categoryMap.get(category); + if (!eventsInCategory) { + eventsInCategory = []; + categoryMap.set(category, eventsInCategory); + } + eventsInCategory.push(event); } - return relatedById.events.reduce( - (eventsByCategory: ResolverEvent[], candidate: ResolverEvent) => { - if ( - [candidate && eventModel.allEventCategories(candidate)].flat().includes(ecsCategory) - ) { - eventsByCategory.push(candidate); - } - return eventsByCategory; - }, - [] - ); - }); - }); + } + } + + // Use the same empty array for all values that are missing + const emptyArray: SafeResolverEvent[] = []; + + return (entityID: string, category: string): SafeResolverEvent[] => { + const categoryMap = nodeMap.get(entityID); + if (!categoryMap) { + return emptyArray; + } + const eventsInCategory = categoryMap.get(category); + return eventsInCategory ?? emptyArray; + }; } ); -/** - * returns a map of entity_ids to booleans indicating if it is waiting on related event - * A value of `undefined` can be interpreted as `not yet requested` - * @deprecated - */ -export function relatedEventsReady(data: DataState): Map { - return data.relatedEventsReady; -} +export const relatedEventCountByType: ( + state: DataState +) => (nodeID: string, eventType: string) => number | undefined = createSelector( + relatedEventsStats, + (statsMap) => { + return (nodeID: string, eventType: string): number | undefined => { + const stats = statsMap(nodeID); + if (stats) { + const value = Object.prototype.hasOwnProperty.call(stats.events.byCategory, eventType); + if (typeof value === 'number' && Number.isFinite(value)) { + return value; + } + } + }; + } +); /** * `true` if there were more children than we got in the last request. @@ -355,113 +300,6 @@ export function hasMoreAncestors(state: DataState): boolean { return resolverTree ? resolverTreeModel.hasMoreAncestors(resolverTree) : false; } -interface RelatedInfoFunctions { - shouldShowLimitForCategory: (category: string) => boolean; - numberNotDisplayedForCategory: (category: string) => number; - numberActuallyDisplayedForCategory: (category: string) => number; -} -/** - * A map of `entity_id`s to functions that provide information about - * related events by ECS `.category` Primarily to avoid having business logic - * in UI components. - * @deprecated - */ -export const relatedEventInfoByEntityId: ( - state: DataState -) => (entityID: string) => RelatedInfoFunctions | null = createSelector( - relatedEventsByEntityId, - relatedEventsStats, - function selectLineageLimitInfo( - /* eslint-disable no-shadow */ - relatedEventsByEntityId, - relatedEventsStats - /* eslint-enable no-shadow */ - ) { - return (entityId) => { - const stats = relatedEventsStats(entityId); - if (!stats) { - return null; - } - const eventsResponseForThisEntry = relatedEventsByEntityId.get(entityId); - const hasMoreEvents = - eventsResponseForThisEntry && eventsResponseForThisEntry.nextEvent !== null; - /** - * Get the "aggregate" total for the event category (i.e. _all_ events that would qualify as being "in category") - * For a set like `[DNS,File][File,DNS][Registry]` The first and second events would contribute to the aggregate total for DNS being 2. - * This is currently aligned with how the backed provides this information. - * - * @param eventCategory {string} The ECS category like 'file','dns',etc. - */ - const aggregateTotalForCategory = (eventCategory: string): number => { - return stats.events.byCategory[eventCategory] || 0; - }; - - /** - * Get all the related events in the category provided. - * - * @param eventCategory {string} The ECS category like 'file','dns',etc. - */ - const unmemoizedMatchingEventsForCategory = (eventCategory: string): ResolverEvent[] => { - if (!eventsResponseForThisEntry) { - return []; - } - return eventsResponseForThisEntry.events.filter((resolverEvent) => { - for (const category of [eventModel.allEventCategories(resolverEvent)].flat()) { - if (category === eventCategory) { - return true; - } - } - return false; - }); - }; - - const matchingEventsForCategory = unmemoizedMatchingEventsForCategory; - - /** - * The number of events that occurred before the API limit was reached. - * The number of events that came back form the API that have `eventCategory` in their list of categories. - * - * @param eventCategory {string} The ECS category like 'file','dns',etc. - */ - const numberActuallyDisplayedForCategory = (eventCategory: string): number => { - return matchingEventsForCategory(eventCategory)?.length || 0; - }; - - /** - * The total number counted by the backend - the number displayed - * - * @param eventCategory {string} The ECS category like 'file','dns',etc. - */ - const numberNotDisplayedForCategory = (eventCategory: string): number => { - return ( - aggregateTotalForCategory(eventCategory) - - numberActuallyDisplayedForCategory(eventCategory) - ); - }; - - /** - * `true` when the `nextEvent` cursor appeared in the results and we are short on the number needed to - * fullfill the aggregate count. - * - * @param eventCategory {string} The ECS category like 'file','dns',etc. - */ - const shouldShowLimitForCategory = (eventCategory: string): boolean => { - if (hasMoreEvents && numberNotDisplayedForCategory(eventCategory) > 0) { - return true; - } - return false; - }; - - const entryValue = { - shouldShowLimitForCategory, - numberNotDisplayedForCategory, - numberActuallyDisplayedForCategory, - }; - return entryValue; - }; - } -); - /** * If the tree resource needs to be fetched then these are the parameters that should be used. */ @@ -470,14 +308,14 @@ export function treeParametersToFetch(state: DataState): TreeFetcherParameters | * If there are current tree parameters that don't match the parameters used in the pending request (if there is a pending request) and that don't match the parameters used in the last completed request (if there was a last completed request) then we need to fetch the tree resource using the current parameters. */ if ( - state.tree.currentParameters !== undefined && + state.tree?.currentParameters !== undefined && !treeFetcherParametersModel.equal( - state.tree.currentParameters, - state.tree.lastResponse?.parameters + state.tree?.currentParameters, + state.tree?.lastResponse?.parameters ) && !treeFetcherParametersModel.equal( - state.tree.currentParameters, - state.tree.pendingRequestParameters + state.tree?.currentParameters, + state.tree?.pendingRequestParameters ) ) { return state.tree.currentParameters; @@ -533,10 +371,11 @@ export const layout = createSelector( */ export const processEventForID: ( state: DataState -) => (nodeID: string) => ResolverEvent | null = createSelector( +) => (nodeID: string) => SafeResolverEvent | null = createSelector( tree, - (indexedProcessTree) => (nodeID: string) => - indexedProcessTreeModel.processEvent(indexedProcessTree, nodeID) as ResolverEvent + (indexedProcessTree) => (nodeID: string) => { + return indexedProcessTreeModel.processEvent(indexedProcessTree, nodeID); + } ); /** @@ -547,7 +386,7 @@ export const ariaLevel: (state: DataState) => (nodeID: string) => number | null processEventForID, ({ ariaLevels }, processEventGetter) => (nodeID: string) => { const node = processEventGetter(nodeID); - return node ? ariaLevels.get(node as SafeResolverEvent) ?? null : null; + return node ? ariaLevels.get(node) ?? null : null; } ); @@ -582,7 +421,7 @@ export const ariaFlowtoCandidate: ( * Getting the following sibling of a node has an `O(n)` time complexity where `n` is the number of children the parent of the node has. * For this reason, we calculate the following siblings of the node and all of its siblings at once and cache them. */ - const nodeEvent: ResolverEvent | null = eventGetter(nodeID); + const nodeEvent: SafeResolverEvent | null = eventGetter(nodeID); if (!nodeEvent) { // this should never happen. @@ -592,23 +431,30 @@ export const ariaFlowtoCandidate: ( // nodes with the same parent ID const children = indexedProcessTreeModel.children( indexedProcessTree, - uniqueParentPidForProcess(nodeEvent) + eventModel.parentEntityIDSafeVersion(nodeEvent) ); - let previousChild: ResolverEvent | null = null; + let previousChild: SafeResolverEvent | null = null; // Loop over all nodes that have the same parent ID (even if the parent ID is undefined or points to a node that isn't in the tree.) for (const child of children) { if (previousChild !== null) { // Set the `child` as the following sibling of `previousChild`. - memo.set(uniquePidForProcess(previousChild), uniquePidForProcess(child as ResolverEvent)); + const previousChildEntityID = eventModel.entityIDSafeVersion(previousChild); + const followingSiblingEntityID = eventModel.entityIDSafeVersion(child); + if (previousChildEntityID !== undefined && followingSiblingEntityID !== undefined) { + memo.set(previousChildEntityID, followingSiblingEntityID); + } } // Set the child as the previous child. - previousChild = child as ResolverEvent; + previousChild = child; } if (previousChild) { // if there is a previous child, it has no following sibling. - memo.set(uniquePidForProcess(previousChild), null); + const entityID = eventModel.entityIDSafeVersion(previousChild); + if (entityID !== undefined) { + memo.set(entityID, null); + } } return memoizedGetter(nodeID); @@ -708,10 +554,10 @@ export function treeRequestParametersToAbort(state: DataState): TreeFetcherParam * If there is a pending request, and its not for the current parameters (even, if the current parameters are undefined) then we should abort the request. */ if ( - state.tree.pendingRequestParameters !== undefined && + state.tree?.pendingRequestParameters !== undefined && !treeFetcherParametersModel.equal( - state.tree.pendingRequestParameters, - state.tree.currentParameters + state.tree?.pendingRequestParameters, + state.tree?.currentParameters ) ) { return state.tree.pendingRequestParameters; @@ -725,19 +571,19 @@ export function treeRequestParametersToAbort(state: DataState): TreeFetcherParam */ export const relatedEventTotalForProcess: ( state: DataState -) => (event: ResolverEvent) => number | null = createSelector( +) => (event: SafeResolverEvent) => number | null = createSelector( relatedEventsStats, (statsForProcess) => { - return (event: ResolverEvent) => { - const stats = statsForProcess(uniquePidForProcess(event)); - if (!stats) { + return (event: SafeResolverEvent) => { + const nodeID = eventModel.entityIDSafeVersion(event); + if (nodeID === undefined) { return null; } - let total = 0; - for (const value of Object.values(stats.events.byCategory)) { - total += value; + const stats = statsForProcess(nodeID); + if (!stats) { + return null; } - return total; + return stats.events.total; }; } ); diff --git a/x-pack/plugins/security_solution/public/resolver/store/data/visible_entities.test.ts b/x-pack/plugins/security_solution/public/resolver/store/data/visible_entities.test.ts index 28948debae891..506acefe51676 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/data/visible_entities.test.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/data/visible_entities.test.ts @@ -8,7 +8,7 @@ import { Store, createStore } from 'redux'; import { ResolverAction } from '../actions'; import { resolverReducer } from '../reducer'; import { ResolverState } from '../../types'; -import { LegacyEndpointEvent, ResolverEvent } from '../../../../common/endpoint/types'; +import { LegacyEndpointEvent, SafeResolverEvent } from '../../../../common/endpoint/types'; import { visibleNodesAndEdgeLines } from '../selectors'; import { mockProcessEvent } from '../../models/process_event_test_helpers'; import { mock as mockResolverTree } from '../../models/resolver_tree'; @@ -102,7 +102,7 @@ describe('resolver visible entities', () => { }); describe('when rendering a large tree with a small viewport', () => { beforeEach(() => { - const events: ResolverEvent[] = [ + const events: SafeResolverEvent[] = [ processA, processB, processC, @@ -130,7 +130,7 @@ describe('resolver visible entities', () => { }); describe('when rendering a large tree with a large viewport', () => { beforeEach(() => { - const events: ResolverEvent[] = [ + const events: SafeResolverEvent[] = [ processA, processB, processC, diff --git a/x-pack/plugins/security_solution/public/resolver/store/methods.ts b/x-pack/plugins/security_solution/public/resolver/store/methods.ts index 8dd15b1a44d0c..f121b2aa86888 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/methods.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/methods.ts @@ -7,7 +7,7 @@ import { animatePanning } from './camera/methods'; import { layout } from './selectors'; import { ResolverState } from '../types'; -import { ResolverEvent, SafeResolverEvent } from '../../../common/endpoint/types'; +import { SafeResolverEvent } from '../../../common/endpoint/types'; const animationDuration = 1000; @@ -17,10 +17,10 @@ const animationDuration = 1000; export function animateProcessIntoView( state: ResolverState, startTime: number, - process: ResolverEvent + process: SafeResolverEvent ): ResolverState { const { processNodePositions } = layout(state); - const position = processNodePositions.get(process as SafeResolverEvent); + const position = processNodePositions.get(process); if (position) { return { ...state, diff --git a/x-pack/plugins/security_solution/public/resolver/store/middleware/index.ts b/x-pack/plugins/security_solution/public/resolver/store/middleware/index.ts index ef6b1f5eb3c6f..5dca858b4fabe 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/middleware/index.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/middleware/index.ts @@ -6,9 +6,10 @@ import { Dispatch, MiddlewareAPI } from 'redux'; import { ResolverState, DataAccessLayer } from '../../types'; -import { ResolverRelatedEvents } from '../../../../common/endpoint/types'; import { ResolverTreeFetcher } from './resolver_tree_fetcher'; + import { ResolverAction } from '../actions'; +import { RelatedEventsFetcher } from './related_events_fetcher'; type MiddlewareFactory = ( dataAccessLayer: DataAccessLayer @@ -25,33 +26,12 @@ type MiddlewareFactory = ( export const resolverMiddlewareFactory: MiddlewareFactory = (dataAccessLayer: DataAccessLayer) => { return (api) => (next) => { const resolverTreeFetcher = ResolverTreeFetcher(dataAccessLayer, api); + const relatedEventsFetcher = RelatedEventsFetcher(dataAccessLayer, api); return async (action: ResolverAction) => { next(action); resolverTreeFetcher(); - - if ( - action.type === 'userRequestedRelatedEventData' || - action.type === 'appDetectedMissingEventData' - ) { - const entityIdToFetchFor = action.payload; - let result: ResolverRelatedEvents | undefined; - try { - result = await dataAccessLayer.relatedEvents(entityIdToFetchFor); - } catch { - api.dispatch({ - type: 'serverFailedToReturnRelatedEventData', - payload: action.payload, - }); - } - - if (result) { - api.dispatch({ - type: 'serverReturnedRelatedEventData', - payload: result, - }); - } - } + relatedEventsFetcher(); }; }; }; diff --git a/x-pack/plugins/security_solution/public/resolver/store/middleware/related_events_fetcher.ts b/x-pack/plugins/security_solution/public/resolver/store/middleware/related_events_fetcher.ts new file mode 100644 index 0000000000000..b83e3cff90736 --- /dev/null +++ b/x-pack/plugins/security_solution/public/resolver/store/middleware/related_events_fetcher.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Dispatch, MiddlewareAPI } from 'redux'; +import { isEqual } from 'lodash'; +import { ResolverRelatedEvents } from '../../../../common/endpoint/types'; + +import { ResolverState, DataAccessLayer, PanelViewAndParameters } from '../../types'; +import * as selectors from '../selectors'; +import { ResolverAction } from '../actions'; + +export function RelatedEventsFetcher( + dataAccessLayer: DataAccessLayer, + api: MiddlewareAPI, ResolverState> +): () => void { + let last: PanelViewAndParameters | undefined; + + // Call this after each state change. + // This fetches the ResolverTree for the current entityID + // if the entityID changes while + return async () => { + const state = api.getState(); + + const newParams = selectors.panelViewAndParameters(state); + const oldParams = last; + // Update this each time before fetching data (or even if we don't fetch data) so that subsequent actions that call this (concurrently) will have up to date info. + last = newParams; + + // If the panel view params have changed and the current panel view is either `nodeEventsOfType` or `eventDetail`, then fetch the related events for that nodeID. + if ( + !isEqual(newParams, oldParams) && + (newParams.panelView === 'nodeEventsOfType' || newParams.panelView === 'eventDetail') + ) { + const nodeID = newParams.panelParameters.nodeID; + + const result: ResolverRelatedEvents | undefined = await dataAccessLayer.relatedEvents(nodeID); + + if (result) { + api.dispatch({ + type: 'serverReturnedRelatedEventData', + payload: result, + }); + } + } + }; +} diff --git a/x-pack/plugins/security_solution/public/resolver/store/reducer.ts b/x-pack/plugins/security_solution/public/resolver/store/reducer.ts index bf62fd0e60df8..ae1e9a58a2097 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/reducer.ts @@ -9,7 +9,7 @@ import { cameraReducer } from './camera/reducer'; import { dataReducer } from './data/reducer'; import { ResolverAction } from './actions'; import { ResolverState, ResolverUIState } from '../types'; -import { uniquePidForProcess } from '../models/process_event'; +import * as eventModel from '../../../common/endpoint/models/event'; const uiReducer: Reducer = ( state = { @@ -37,17 +37,18 @@ const uiReducer: Reducer = ( selectedNode: action.payload, }; return next; - } else if ( - action.type === 'userBroughtProcessIntoView' || - action.type === 'appDetectedNewIdFromQueryParams' - ) { - const nodeID = uniquePidForProcess(action.payload.process); - const next: ResolverUIState = { - ...state, - ariaActiveDescendant: nodeID, - selectedNode: nodeID, - }; - return next; + } else if (action.type === 'userBroughtProcessIntoView') { + const nodeID = eventModel.entityIDSafeVersion(action.payload.process); + if (nodeID !== undefined) { + const next: ResolverUIState = { + ...state, + ariaActiveDescendant: nodeID, + selectedNode: nodeID, + }; + return next; + } else { + return state; + } } else if (action.type === 'appReceivedNewExternalProperties') { const next: ResolverUIState = { ...state, @@ -68,10 +69,7 @@ const concernReducers = combineReducers({ export const resolverReducer: Reducer = (state, action) => { const nextState = concernReducers(state, action); - if ( - action.type === 'userBroughtProcessIntoView' || - action.type === 'appDetectedNewIdFromQueryParams' - ) { + if (action.type === 'userBroughtProcessIntoView') { return animateProcessIntoView(nextState, action.payload.time, action.payload.process); } else { return nextState; diff --git a/x-pack/plugins/security_solution/public/resolver/store/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/selectors.ts index 96b080206b61e..3c99a186ac0c2 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/selectors.ts @@ -9,7 +9,7 @@ import * as cameraSelectors from './camera/selectors'; import * as dataSelectors from './data/selectors'; import * as uiSelectors from './ui/selectors'; import { ResolverState, IsometricTaxiLayout } from '../types'; -import { ResolverEvent, ResolverNodeStats } from '../../../common/endpoint/types'; +import { ResolverNodeStats, SafeResolverEvent } from '../../../common/endpoint/types'; import { entityIDSafeVersion } from '../../../common/endpoint/models/event'; /** @@ -61,6 +61,11 @@ export const isProcessTerminated = composeSelectors( dataSelectors.isProcessTerminated ); +/** + * Retrieve an event from memory using the event's ID. + */ +export const eventByID = composeSelectors(dataStateSelector, dataSelectors.eventByID); + /** * Given a nodeID (aka entity_id) get the indexed process event. * Legacy functions take process events instead of nodeID, use this to get @@ -68,7 +73,7 @@ export const isProcessTerminated = composeSelectors( */ export const processEventForID: ( state: ResolverState -) => (nodeID: string) => ResolverEvent | null = composeSelectors( +) => (nodeID: string) => SafeResolverEvent | null = composeSelectors( dataStateSelector, dataSelectors.processEventForID ); @@ -119,30 +124,27 @@ export const relatedEventsStats: ( * of their individual `event.category`s. E.g. a [DNS, Network] would count as two * towards the aggregate total. */ -export const relatedEventAggregateTotalByEntityId: ( +export const relatedEventTotalCount: ( state: ResolverState -) => (nodeID: string) => number = composeSelectors( +) => (nodeID: string) => number | undefined = composeSelectors( dataStateSelector, - dataSelectors.relatedEventAggregateTotalByEntityId + dataSelectors.relatedEventTotalCount ); -/** - * Map of related events... by entity id - * @deprecated - */ -export const relatedEventsByEntityId = composeSelectors( +export const relatedEventCountByType: ( + state: ResolverState +) => (nodeID: string, eventType: string) => number | undefined = composeSelectors( dataStateSelector, - dataSelectors.relatedEventsByEntityId + dataSelectors.relatedEventCountByType ); /** - * Returns a function that returns the information needed to display related event details based on - * the related event's entityID and its own ID. + * Map of related events... by entity id * @deprecated */ -export const relatedEventDisplayInfoByEntityAndSelfId = composeSelectors( +export const relatedEventsByEntityId = composeSelectors( dataStateSelector, - dataSelectors.relatedEventDisplayInfoByEntityAndSelfID + dataSelectors.relatedEventsByEntityId ); /** @@ -155,26 +157,6 @@ export const relatedEventsByCategory = composeSelectors( dataSelectors.relatedEventsByCategory ); -/** - * Entity ids to booleans for waiting status - * @deprecated - */ -export const relatedEventsReady = composeSelectors( - dataStateSelector, - dataSelectors.relatedEventsReady -); - -/** - * Business logic lookup functions by ECS category by entity id. - * Example usage: - * const numberOfFileEvents = infoByEntityId.get(`someEntityId`)?.getAggregateTotalForCategory(`file`); - * @deprecated - */ -export const relatedEventInfoByEntityId = composeSelectors( - dataStateSelector, - dataSelectors.relatedEventInfoByEntityId -); - /** * Returns the id of the "current" tree node (fake-focused) */ diff --git a/x-pack/plugins/security_solution/public/resolver/store/ui/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/ui/selectors.ts index 6bc41832b92f2..a8882d835fce1 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/ui/selectors.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/ui/selectors.ts @@ -8,9 +8,9 @@ import { decode, encode } from 'rison-node'; import { createSelector } from 'reselect'; import { PanelViewAndParameters, ResolverUIState } from '../../types'; -import { ResolverEvent } from '../../../../common/endpoint/types'; +import { SafeResolverEvent } from '../../../../common/endpoint/types'; import { isPanelViewAndParameters } from '../../models/location_search'; -import { eventId } from '../../../../common/endpoint/models/event'; +import { eventID } from '../../../../common/endpoint/models/event'; /** * id of the "current" tree node (fake-focused) @@ -124,12 +124,12 @@ export const relatedEventDetailHrefs: ( ) => ( category: string, nodeID: string, - events: ResolverEvent[] + events: SafeResolverEvent[] ) => Map = createSelector(relativeHref, (relativeHref) => { - return (category: string, nodeID: string, events: ResolverEvent[]) => { + return (category: string, nodeID: string, events: SafeResolverEvent[]) => { const hrefsByEntityID = new Map(); events.map((event) => { - const entityID = String(eventId(event)); + const entityID = String(eventID(event)); const eventDetailPanelParams: PanelViewAndParameters = { panelView: 'eventDetail', panelParameters: { diff --git a/x-pack/plugins/security_solution/public/resolver/types.ts b/x-pack/plugins/security_solution/public/resolver/types.ts index 952a1c5764d8e..4dc614abe3345 100644 --- a/x-pack/plugins/security_solution/public/resolver/types.ts +++ b/x-pack/plugins/security_solution/public/resolver/types.ts @@ -211,9 +211,8 @@ export interface TreeFetcherParameters { */ export interface DataState { readonly relatedEvents: Map; - readonly relatedEventsReady: Map; - readonly tree: { + readonly tree?: { /** * The parameters passed from the resolver properties */ @@ -614,8 +613,9 @@ export interface ResolverPluginSetup { dataAccessLayer: { /** * A mock `DataAccessLayer` that returns a tree that has no ancestor nodes but which has 2 children nodes. + * The origin has 2 related registry events */ - noAncestorsTwoChildren: () => { dataAccessLayer: DataAccessLayer }; + noAncestorsTwoChildrenWithRelatedEventsOnOrigin: () => { dataAccessLayer: DataAccessLayer }; }; }; } diff --git a/x-pack/plugins/security_solution/public/resolver/view/edge_line.tsx b/x-pack/plugins/security_solution/public/resolver/view/edge_line.tsx index 53b889004798f..777a7292e9c23 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/edge_line.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/edge_line.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + import React from 'react'; import styled from 'styled-components'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -53,7 +55,7 @@ const StyledElapsedTime = styled.div` /** * A placeholder line segment view that connects process nodes. */ -const EdgeLineComponent = React.memo( +export const EdgeLine = React.memo( ({ className, edgeLineMetadata, @@ -155,7 +157,3 @@ const EdgeLineComponent = React.memo( ); } ); - -EdgeLineComponent.displayName = 'EdgeLine'; - -export const EdgeLine = EdgeLineComponent; diff --git a/x-pack/plugins/security_solution/public/resolver/view/graph_controls.tsx b/x-pack/plugins/security_solution/public/resolver/view/graph_controls.tsx index 75aecf6747cca..dbeca840a4b66 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/graph_controls.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/graph_controls.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + /* eslint-disable react/button-has-type */ import React, { useCallback, useMemo, useContext } from 'react'; @@ -54,7 +56,7 @@ const StyledGraphControls = styled.div` /** * Controls for zooming, panning, and centering in Resolver */ -const GraphControlsComponent = React.memo( +export const GraphControls = React.memo( ({ className, }: { @@ -204,7 +206,3 @@ const GraphControlsComponent = React.memo( ); } ); - -GraphControlsComponent.displayName = 'GraphControlsComponent'; - -export const GraphControls = GraphControlsComponent; diff --git a/x-pack/plugins/security_solution/public/resolver/view/limit_warnings.tsx b/x-pack/plugins/security_solution/public/resolver/view/limit_warnings.tsx index 3f2b7c769cad7..bc57c4e28b9cd 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/limit_warnings.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/limit_warnings.tsx @@ -4,9 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + import React from 'react'; -import { EuiCallOut } from '@elastic/eui'; import { FormattedMessage } from 'react-intl'; +import { LimitWarningsEuiCallOut } from './styles'; const lineageLimitMessage = ( ); -const LineageTitleMessage = React.memo(function LineageTitleMessage({ - numberOfEntries, -}: { - numberOfEntries: number; -}) { +const LineageTitleMessage = React.memo(function ({ numberOfEntries }: { numberOfEntries: number }) { return (

- + ); }); /** * Limit warning for hitting a limit of nodes in the tree */ -export const LimitWarning = React.memo(function LimitWarning({ - className, - numberDisplayed, -}: { - className?: string; - numberDisplayed: number; -}) { +export const LimitWarning = React.memo(function ({ numberDisplayed }: { numberDisplayed: number }) { return ( - } >

{lineageLimitMessage}

-
+ ); }); diff --git a/x-pack/plugins/security_solution/public/resolver/view/node.test.tsx b/x-pack/plugins/security_solution/public/resolver/view/node.test.tsx new file mode 100644 index 0000000000000..0b381f6771f00 --- /dev/null +++ b/x-pack/plugins/security_solution/public/resolver/view/node.test.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { noAncestorsTwoChildren } from '../data_access_layer/mocks/no_ancestors_two_children'; +import { Simulator } from '../test_utilities/simulator'; +// Extend jest with a custom matcher +import '../test_utilities/extend_jest'; + +let simulator: Simulator; +let databaseDocumentID: string; + +// the resolver component instance ID, used by the react code to distinguish piece of global state from those used by other resolver instances +const resolverComponentInstanceID = 'resolverComponentInstanceID'; + +describe('Resolver, when analyzing a tree that has no ancestors and 2 children', () => { + beforeEach(async () => { + // create a mock data access layer + const { metadata: dataAccessLayerMetadata, dataAccessLayer } = noAncestorsTwoChildren(); + + // save a reference to the `_id` supported by the mock data layer + databaseDocumentID = dataAccessLayerMetadata.databaseDocumentID; + + // create a resolver simulator, using the data access layer and an arbitrary component instance ID + simulator = new Simulator({ + databaseDocumentID, + dataAccessLayer, + resolverComponentInstanceID, + indices: [], + }); + }); + + it('shows 1 node with the words "Analyzed Event" in the label', async () => { + await expect( + simulator.map(() => { + return simulator.testSubject('resolver:node:description').map((element) => element.text()); + }) + ).toYieldEqualTo(['Analyzed Event · Running Process', 'Running Process', 'Running Process']); + }); +}); diff --git a/x-pack/plugins/security_solution/public/resolver/view/panel.test.tsx b/x-pack/plugins/security_solution/public/resolver/view/panel.test.tsx index 7cfbd9a794669..2f23469606aca 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panel.test.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panel.test.tsx @@ -5,7 +5,7 @@ */ import { createMemoryHistory, History as HistoryPackageHistoryInterface } from 'history'; -import { noAncestorsTwoChildren } from '../data_access_layer/mocks/no_ancestors_two_children'; +import { noAncestorsTwoChildrenWithRelatedEventsOnOrigin } from '../data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin'; import { Simulator } from '../test_utilities/simulator'; // Extend jest with a custom matcher import '../test_utilities/extend_jest'; @@ -14,7 +14,7 @@ import { urlSearch } from '../test_utilities/url_search'; // the resolver component instance ID, used by the react code to distinguish piece of global state from those used by other resolver instances const resolverComponentInstanceID = 'resolverComponentInstanceID'; -describe(`Resolver: when analyzing a tree with no ancestors and two children, and when the component instance ID is ${resolverComponentInstanceID}`, () => { +describe(`Resolver: when analyzing a tree with no ancestors and two children and two related registry event on the origin, and when the component instance ID is ${resolverComponentInstanceID}`, () => { /** * Get (or lazily create and get) the simulator. */ @@ -32,7 +32,10 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children, an beforeEach(() => { // create a mock data access layer - const { metadata: dataAccessLayerMetadata, dataAccessLayer } = noAncestorsTwoChildren(); + const { + metadata: dataAccessLayerMetadata, + dataAccessLayer, + } = noAncestorsTwoChildrenWithRelatedEventsOnOrigin(); entityIDs = dataAccessLayerMetadata.entityIDs; @@ -184,6 +187,38 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children, an }) ); }); + describe("and when the user clicks the link to the node's events", () => { + beforeEach(async () => { + const nodeEventsListLink = await simulator().resolve( + 'resolver:node-detail:node-events-link' + ); + + if (nodeEventsListLink) { + nodeEventsListLink.simulate('click', { button: 0 }); + } + }); + it('should show a link to view 2 registry events', async () => { + await expect( + simulator().map(() => { + // The link text is split across two columns. The first column is the count and the second column has the type. + const type = simulator().testSubject('resolver:panel:node-events:event-type-count'); + const link = simulator().testSubject('resolver:panel:node-events:event-type-link'); + return { + typeLength: type.length, + linkLength: link.length, + typeText: type.text(), + linkText: link.text(), + }; + }) + ).toYieldEqualTo({ + typeLength: 1, + linkLength: 1, + linkText: 'registry', + // EUI's Table adds the column name to the value. + typeText: 'Count2', + }); + }); + }); describe('and when the node list link has been clicked', () => { beforeEach(async () => { const nodeListLink = await simulator().resolve( diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/breadcrumbs.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/breadcrumbs.tsx new file mode 100644 index 0000000000000..ed39198009364 --- /dev/null +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/breadcrumbs.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable react/display-name */ + +import { i18n } from '@kbn/i18n'; +import { EuiBreadcrumb, EuiBetaBadge } from '@elastic/eui'; +import React, { memo } from 'react'; +import { BetaHeader, ThemedBreadcrumbs } from './styles'; +import { useColors } from '../use_colors'; + +/** + * Breadcrumb menu + */ +export const Breadcrumbs = memo(function ({ breadcrumbs }: { breadcrumbs: EuiBreadcrumb[] }) { + const { resolverBreadcrumbBackground, resolverEdgeText } = useColors(); + return ( + <> + + + + + + ); +}); diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/cube_for_process.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/cube_for_process.tsx index 4e9d64f5a76a4..cc5f39e985d9e 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/cube_for_process.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/cube_for_process.tsx @@ -43,7 +43,7 @@ export const CubeForProcess = memo(function ({ className={className} width="2.15em" height="2.15em" - viewBox="0 0 100% 100%" + viewBox="0 0 34 34" data-test-subj={dataTestSubj} isOrigin={isOrigin} > diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/deep_object_entries.test.ts b/x-pack/plugins/security_solution/public/resolver/view/panels/deep_object_entries.test.ts new file mode 100644 index 0000000000000..1c4e1f4199bc4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/deep_object_entries.test.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { deepObjectEntries } from './deep_object_entries'; + +describe('deepObjectEntries', () => { + const valuesAndExpected: Array<[ + objectValue: object, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expected: Array<[path: Array, fieldValue: unknown]> + ]> = [ + [{}, []], // No 'field' values found + [{ a: {} }, []], // No 'field' values found + [{ a: { b: undefined } }, []], // No 'field' values found + [{ a: { b: undefined, c: [] } }, []], // No 'field' values found + [{ a: { b: undefined, c: [null] } }, []], // No 'field' values found + [{ a: { b: undefined, c: [null, undefined, 1] } }, [[['a', 'c'], 1]]], // Only `1` is a non-null value. It is under `a.c` because we ignore array indices + [ + { a: { b: undefined, c: [null, undefined, 1, { d: ['e'] }] } }, + [ + // 1 and 'e' are valid fields. + [['a', 'c'], 1], + [['a', 'c', 'd'], 'e'], + ], + ], + ]; + + describe.each(valuesAndExpected)('when passed %j', (value, expected) => { + it(`should return ${JSON.stringify(expected)}`, () => { + expect(deepObjectEntries(value)).toEqual(expected); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/deep_object_entries.ts b/x-pack/plugins/security_solution/public/resolver/view/panels/deep_object_entries.ts new file mode 100644 index 0000000000000..a508b00be5739 --- /dev/null +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/deep_object_entries.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; + * you may not use this file except in compliance with the Elastic License. + */ +/** + * Sort of like object entries, but does a DFS of an object. + * Instead of getting a key, an array of keys is returned. + * The array of keys represents the path to the value. + * `undefined` and `null` values are omitted. + */ +export function deepObjectEntries(root: object): Array<[path: string[], value: unknown]> { + const queue: Array<{ path: string[]; value: unknown }> = [{ path: [], value: root }]; + const result: Array<[path: string[], value: unknown]> = []; + while (queue.length) { + const next = queue.shift(); + if (next === undefined) { + // this should be impossible + throw new Error(); + } + const { path, value } = next; + if (Array.isArray(value)) { + // branch on arrays + queue.push( + ...value.map((element) => ({ + path: [...path], // unlike with object paths, don't add the number indices to `path` + value: element, + })) + ); + } else if (typeof value === 'object' && value !== null) { + // branch on non-null objects + queue.push( + ...Object.keys(value).map((key) => ({ + path: [...path, key], + value: (value as Record)[key], + })) + ); + } else if (value !== undefined && value !== null) { + // emit other non-null, defined, values + result.push([path, value]); + } + } + return result; +} diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/descriptive_name.test.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/descriptive_name.test.tsx new file mode 100644 index 0000000000000..e869ab1ecd456 --- /dev/null +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/descriptive_name.test.tsx @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data'; +import { DescriptiveName } from './descriptive_name'; +import { SafeResolverEvent } from '../../../../common/endpoint/types'; +import { mount, ReactWrapper } from 'enzyme'; +import { I18nProvider } from '@kbn/i18n/react'; + +describe('DescriptiveName', () => { + let generator: EndpointDocGenerator; + let wrapper: (event: SafeResolverEvent) => ReactWrapper; + beforeEach(() => { + generator = new EndpointDocGenerator('seed'); + wrapper = (event: SafeResolverEvent) => + mount( + + + + ); + }); + it('returns the right name for a registry event', () => { + const extensions = { registry: { key: `HKLM/Windows/Software/abc` } }; + const event = generator.generateEvent({ eventCategory: 'registry', extensions }); + expect(wrapper(event).text()).toEqual(`HKLM/Windows/Software/abc`); + }); + + it('returns the right name for a network event', () => { + const randomIP = `${generator.randomIP()}`; + const extensions = { network: { direction: 'outbound', forwarded_ip: randomIP } }; + const event = generator.generateEvent({ eventCategory: 'network', extensions }); + expect(wrapper(event).text()).toEqual(`outbound ${randomIP}`); + }); + + it('returns the right name for a file event', () => { + const extensions = { file: { path: 'C:\\My Documents\\business\\January\\processName' } }; + const event = generator.generateEvent({ eventCategory: 'file', extensions }); + expect(wrapper(event).text()).toEqual('C:\\My Documents\\business\\January\\processName'); + }); + + it('returns the right name for a dns event', () => { + const extensions = { dns: { question: { name: `${generator.randomIP()}` } } }; + const event = generator.generateEvent({ eventCategory: 'dns', extensions }); + expect(wrapper(event).text()).toEqual(extensions.dns.question.name); + }); +}); diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/descriptive_name.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/descriptive_name.tsx new file mode 100644 index 0000000000000..195ebceee0610 --- /dev/null +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/descriptive_name.tsx @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FormattedMessage } from 'react-intl'; + +import React from 'react'; + +import { + isLegacyEventSafeVersion, + processNameSafeVersion, + entityIDSafeVersion, +} from '../../../../common/endpoint/models/event'; +import { SafeResolverEvent } from '../../../../common/endpoint/types'; + +/** + * Based on the ECS category of the event, attempt to provide a more descriptive name + * (e.g. the `event.registry.key` for `registry` or the `dns.question.name` for `dns`, etc.). + * This function returns the data in the form of `{subject, descriptor}` where `subject` will + * tend to be the more distinctive term (e.g. 137.213.212.7 for a network event) and the + * `descriptor` can be used to present more useful/meaningful view (e.g. `inbound 137.213.212.7` + * in the example above). + * see: https://www.elastic.co/guide/en/ecs/current/ecs-field-reference.html + * @param event The ResolverEvent to get the descriptive name for + */ +export function DescriptiveName({ event }: { event: SafeResolverEvent }) { + if (isLegacyEventSafeVersion(event)) { + return ( + + ); + } + + /** + * This list of attempts can be expanded/adjusted as the underlying model changes over time: + */ + + // Stable, per ECS 1.5: https://www.elastic.co/guide/en/ecs/current/ecs-allowed-values-event-category.html + + if (event.network?.forwarded_ip) { + return ( + + ); + } + + if (event.file?.path) { + return ( + + ); + } + + if (event.registry?.path) { + return ( + + ); + } + + if (event.registry?.key) { + return ( + + ); + } + + if (event.dns?.question?.name) { + return ( + + ); + } + return ( + + ); +} diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/event_detail.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/event_detail.tsx index 24d2a4a8f43f0..72f0d54d51fa3 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/event_detail.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/event_detail.tsx @@ -4,275 +4,103 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { memo, useMemo, useEffect, Fragment } from 'react'; +/* eslint-disable no-continue */ + +/* eslint-disable react/display-name */ + +import React, { memo, useMemo, Fragment } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiSpacer, EuiText, EuiDescriptionList, EuiTextColor, EuiTitle } from '@elastic/eui'; import styled from 'styled-components'; import { useSelector } from 'react-redux'; import { FormattedMessage } from 'react-intl'; import { StyledPanel } from '../styles'; -import { StyledBreadcrumbs, BoldCode, StyledTime, GeneratedText } from './panel_content_utilities'; -import * as event from '../../../../common/endpoint/models/event'; +import { BoldCode, StyledTime, GeneratedText, formatDate } from './panel_content_utilities'; +import { Breadcrumbs } from './breadcrumbs'; +import * as eventModel from '../../../../common/endpoint/models/event'; import * as selectors from '../../store/selectors'; -import { useResolverDispatch } from '../use_resolver_dispatch'; -import { PanelContentError } from './panel_content_error'; import { PanelLoading } from './panel_loading'; import { ResolverState } from '../../types'; -import { useNavigateOrReplace } from '../use_navigate_or_replace'; - -// Adding some styles to prevent horizontal scrollbars, per request from UX review -const StyledDescriptionList = memo(styled(EuiDescriptionList)` - &.euiDescriptionList.euiDescriptionList--column dt.euiDescriptionList__title.desc-title { - max-width: 8em; - overflow-wrap: break-word; - } - &.euiDescriptionList.euiDescriptionList--column dd.euiDescriptionList__description { - max-width: calc(100% - 8.5em); - overflow-wrap: break-word; - } -`); - -// Also prevents horizontal scrollbars on long descriptive names -const StyledDescriptiveName = memo(styled(EuiText)` - padding-right: 1em; - overflow-wrap: break-word; -`); - -// Styling subtitles, per UX review: -const StyledFlexTitle = memo(styled('h3')` - display: flex; - flex-flow: row; - font-size: 1.2em; -`); -const StyledTitleRule = memo(styled('hr')` - &.euiHorizontalRule.euiHorizontalRule--full.euiHorizontalRule--marginSmall.override { - display: block; - flex: 1; - margin-left: 0.5em; - } -`); +import { DescriptiveName } from './descriptive_name'; +import { useLinkProps } from '../use_link_props'; +import { SafeResolverEvent } from '../../../../common/endpoint/types'; +import { deepObjectEntries } from './deep_object_entries'; -const TitleHr = memo(() => { - return ( - +export const EventDetail = memo(function EventDetail({ + nodeID, + eventID, + eventType, +}: { + nodeID: string; + eventID: string; + /** The event type to show in the breadcrumbs */ + eventType: string; +}) { + const event = useSelector((state: ResolverState) => + selectors.eventByID(state)({ nodeID, eventID }) ); + const processEvent = useSelector((state: ResolverState) => + selectors.processEventForID(state)(nodeID) + ); + if (event && processEvent) { + return ( + + ); + } else { + return ( + + + + ); + } }); -TitleHr.displayName = 'TitleHR'; - -/** - * Take description list entries and prepare them for display by - * seeding with `` tags. - * - * @param entries {title: string, description: string}[] - */ -function entriesForDisplay(entries: Array<{ title: string; description: string }>) { - return entries.map((entry) => { - return { - description: {entry.description}, - title: {entry.title}, - }; - }); -} /** * This view presents a detailed view of all the available data for a related event, split and titled by the "section" * it appears in the underlying ResolverEvent */ -export const EventDetail = memo(function ({ +const EventDetailContents = memo(function ({ nodeID, - eventID, + event, + eventType, + processEvent, }: { nodeID: string; - eventID: string; -}) { - const parentEvent = useSelector((state: ResolverState) => - selectors.processEventForID(state)(nodeID) - ); - - const relatedEventsStats = useSelector((state: ResolverState) => - selectors.relatedEventsStats(state)(nodeID) - ); - const countForParent: number = Object.values(relatedEventsStats?.events.byCategory || {}).reduce( - (sum, val) => sum + val, - 0 - ); - const processName = (parentEvent && event.eventName(parentEvent)) || '*'; - const processEntityId = (parentEvent && event.entityId(parentEvent)) || ''; - const totalCount = countForParent || 0; - const eventsString = i18n.translate( - 'xpack.securitySolution.endpoint.resolver.panel.relatedEventDetail.events', - { - defaultMessage: 'Events', - } - ); - const naString = i18n.translate( - 'xpack.securitySolution.endpoint.resolver.panel.relatedEventDetail.NA', - { - defaultMessage: 'N/A', - } - ); - - const relatedsReadyMap = useSelector(selectors.relatedEventsReady); - const relatedsReady = relatedsReadyMap.get(processEntityId!); - const dispatch = useResolverDispatch(); - const nodesHref = useSelector((state: ResolverState) => - selectors.relativeHref(state)({ panelView: 'nodes' }) - ); - const nodesLinkNavProps = useNavigateOrReplace({ - search: nodesHref, - }); - + event: SafeResolverEvent; /** - * If we don't have the related events for the parent yet, use this effect - * to request them. + * Event type to use in the breadcrumbs */ - useEffect(() => { - if ( - typeof relatedsReady === 'undefined' && - processEntityId !== null && - processEntityId !== undefined - ) { - dispatch({ - type: 'appDetectedMissingEventData', - payload: processEntityId, - }); + eventType: string; + processEvent: SafeResolverEvent; +}) { + const formattedDate = useMemo(() => { + const timestamp = eventModel.timestampSafeVersion(event); + if (timestamp !== undefined) { + return formatDate(new Date(timestamp)); } - }, [relatedsReady, dispatch, processEntityId]); - - const [ - relatedEventToShowDetailsFor, - countBySameCategory, - relatedEventCategory = naString, - sections, - formattedDate, - ] = useSelector((state: ResolverState) => - selectors.relatedEventDisplayInfoByEntityAndSelfId(state)(nodeID, eventID) - ); - - const { subject = '', descriptor = '' } = relatedEventToShowDetailsFor - ? event.descriptiveName(relatedEventToShowDetailsFor) - : {}; - - const nodeDetailHref = useSelector((state: ResolverState) => - selectors.relativeHref(state)({ - panelView: 'nodeDetail', - panelParameters: { nodeID: processEntityId }, - }) - ); - const nodeDetailLinkNavProps = useNavigateOrReplace({ - search: nodeDetailHref, - }); - - const nodeEventsHref = useSelector((state: ResolverState) => - selectors.relativeHref(state)({ - panelView: 'nodeEvents', - panelParameters: { nodeID: processEntityId }, - }) - ); - const nodeEventsLinkNavProps = useNavigateOrReplace({ - search: nodeEventsHref, - }); - - const nodeEventsOfTypeHref = useSelector((state: ResolverState) => - selectors.relativeHref(state)({ - panelView: 'nodeEventsOfType', - panelParameters: { nodeID: processEntityId, eventType: relatedEventCategory }, - }) - ); - const nodeEventsOfTypeLinkNavProps = useNavigateOrReplace({ - search: nodeEventsOfTypeHref, - }); - const crumbs = useMemo(() => { - return [ - { - text: eventsString, - ...nodesLinkNavProps, - }, - { - text: processName, - ...nodeDetailLinkNavProps, - }, - { - text: ( - <> - - - ), - ...nodeEventsLinkNavProps, - }, - { - text: ( - <> - - - ), - ...nodeEventsOfTypeLinkNavProps, - }, - { - text: relatedEventToShowDetailsFor ? ( - - ) : ( - naString - ), - onClick: () => {}, - }, - ]; - }, [ - processName, - eventsString, - totalCount, - countBySameCategory, - naString, - relatedEventCategory, - relatedEventToShowDetailsFor, - subject, - descriptor, - nodeEventsOfTypeLinkNavProps, - nodeEventsLinkNavProps, - nodeDetailLinkNavProps, - nodesLinkNavProps, - ]); - - if (!relatedsReady) { - return ; - } - - /** - * Could happen if user e.g. loads a URL with a bad crumbEvent - */ - if (!relatedEventToShowDetailsFor) { - const errString = i18n.translate( - 'xpack.securitySolution.endpoint.resolver.panel.relatedDetail.missing', - { - defaultMessage: 'Related event not found.', - } - ); - return ; - } + }, [event]); return ( - + @@ -288,23 +116,49 @@ export const EventDetail = memo(function ({ - + - {sections.map(({ sectionTitle, entries }, index) => { - const displayEntries = entriesForDisplay(entries); + + + ); +}); + +function EventDetailFields({ event }: { event: SafeResolverEvent }) { + const sections = useMemo(() => { + const returnValue: Array<{ + namespace: React.ReactNode; + descriptions: Array<{ title: React.ReactNode; description: React.ReactNode }>; + }> = []; + for (const [key, value] of Object.entries(event)) { + // ignore these keys + if (key === 'agent' || key === 'ecs' || key === 'process' || key === '@timestamp') { + continue; + } + + const section = { + // Group the fields by their top-level namespace + namespace: {key}, + descriptions: deepObjectEntries(value).map(([path, fieldValue]) => ({ + title: {path.join('.')}, + description: {String(fieldValue)}, + })), + }; + returnValue.push(section); + } + return returnValue; + }, [event]); + return ( + <> + {sections.map(({ namespace, descriptions }, index) => { return ( {index === 0 ? null : } - {sectionTitle} + {namespace} @@ -315,12 +169,136 @@ export const EventDetail = memo(function ({ align="left" titleProps={{ className: 'desc-title' }} compressed - listItems={displayEntries} + listItems={descriptions} /> {index === sections.length - 1 ? null : } ); })} - + + ); +} + +function EventDetailBreadcrumbs({ + nodeID, + nodeName, + event, + breadcrumbEventCategory, +}: { + nodeID: string; + nodeName?: string; + event: SafeResolverEvent; + breadcrumbEventCategory: string; +}) { + const countByCategory = useSelector((state: ResolverState) => + selectors.relatedEventCountByType(state)(nodeID, breadcrumbEventCategory) + ); + const relatedEventCount: number | undefined = useSelector((state: ResolverState) => + selectors.relatedEventTotalCount(state)(nodeID) + ); + const nodesLinkNavProps = useLinkProps({ + panelView: 'nodes', + }); + + const nodeDetailLinkNavProps = useLinkProps({ + panelView: 'nodeDetail', + panelParameters: { nodeID }, + }); + + const nodeEventsLinkNavProps = useLinkProps({ + panelView: 'nodeEvents', + panelParameters: { nodeID }, + }); + + const nodeEventsOfTypeLinkNavProps = useLinkProps({ + panelView: 'nodeEventsOfType', + panelParameters: { nodeID, eventType: breadcrumbEventCategory }, + }); + const breadcrumbs = useMemo(() => { + return [ + { + text: i18n.translate( + 'xpack.securitySolution.endpoint.resolver.panel.relatedEventDetail.events', + { + defaultMessage: 'Events', + } + ), + ...nodesLinkNavProps, + }, + { + text: nodeName, + ...nodeDetailLinkNavProps, + }, + { + text: ( + + ), + ...nodeEventsLinkNavProps, + }, + { + text: ( + + ), + ...nodeEventsOfTypeLinkNavProps, + }, + { + text: , + }, + ]; + }, [ + breadcrumbEventCategory, + countByCategory, + event, + nodeDetailLinkNavProps, + nodeEventsLinkNavProps, + nodeName, + relatedEventCount, + nodesLinkNavProps, + nodeEventsOfTypeLinkNavProps, + ]); + return ; +} + +const StyledDescriptionList = memo(styled(EuiDescriptionList)` + &.euiDescriptionList.euiDescriptionList--column dt.euiDescriptionList__title.desc-title { + max-width: 8em; + overflow-wrap: break-word; + } + &.euiDescriptionList.euiDescriptionList--column dd.euiDescriptionList__description { + max-width: calc(100% - 8.5em); + overflow-wrap: break-word; + } +`); + +// Also prevents horizontal scrollbars on long descriptive names +const StyledDescriptiveName = memo(styled(EuiText)` + padding-right: 1em; + overflow-wrap: break-word; +`); + +const StyledFlexTitle = memo(styled('h3')` + display: flex; + flex-flow: row; + font-size: 1.2em; +`); +const StyledTitleRule = memo(styled('hr')` + &.euiHorizontalRule.euiHorizontalRule--full.euiHorizontalRule--marginSmall.override { + display: block; + flex: 1; + margin-left: 0.5em; + } +`); + +const TitleHr = memo(() => { + return ( + ); }); diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/index.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/index.tsx index da5cb1acfed6d..df9cbe9ced541 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/index.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/index.tsx @@ -11,16 +11,13 @@ import { useSelector } from 'react-redux'; import * as selectors from '../../store/selectors'; import { NodeEventsOfType } from './node_events_of_type'; import { NodeEvents } from './node_events'; -import { NodeDetail } from './node_details'; +import { NodeDetail } from './node_detail'; import { NodeList } from './node_list'; import { EventDetail } from './event_detail'; import { PanelViewAndParameters } from '../../types'; /** - * - * This component implements the strategy laid out above by determining the "right" view and doing some other housekeeping e.g. effects to keep the UI-selected node in line with what's indicated by the URL parameters. - * - * @returns {JSX.Element} The "right" table content to show based on the query params as described above + * Show the panel that matches the `panelViewAndParameters` (derived from the browser's location.search) */ export const PanelRouter = memo(function () { const params: PanelViewAndParameters = useSelector(selectors.panelViewAndParameters); @@ -40,6 +37,7 @@ export const PanelRouter = memo(function () { ); } else { diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/node_details.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/node_detail.tsx similarity index 74% rename from x-pack/plugins/security_solution/public/resolver/view/panels/node_details.tsx rename to x-pack/plugins/security_solution/public/resolver/view/panels/node_detail.tsx index 48d5089eb5641..04e9de61f6256 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/node_details.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/node_detail.tsx @@ -15,23 +15,17 @@ import styled from 'styled-components'; import { EuiDescriptionListProps } from '@elastic/eui/src/components/description_list/description_list'; import { StyledDescriptionList, StyledTitle } from './styles'; import * as selectors from '../../store/selectors'; -import * as event from '../../../../common/endpoint/models/event'; -import { formatDate, StyledBreadcrumbs, GeneratedText } from './panel_content_utilities'; -import { - processPath, - processPid, - userInfoForProcess, - processParentPid, - md5HashForProcess, - argsForProcess, -} from '../../models/process_event'; +import * as eventModel from '../../../../common/endpoint/models/event'; +import { formatDate, GeneratedText } from './panel_content_utilities'; +import { Breadcrumbs } from './breadcrumbs'; +import { processPath, processPID } from '../../models/process_event'; import { CubeForProcess } from './cube_for_process'; -import { ResolverEvent } from '../../../../common/endpoint/types'; +import { SafeResolverEvent } from '../../../../common/endpoint/types'; import { useCubeAssets } from '../use_cube_assets'; import { ResolverState } from '../../types'; import { PanelLoading } from './panel_loading'; import { StyledPanel } from '../styles'; -import { useNavigateOrReplace } from '../use_navigate_or_replace'; +import { useLinkProps } from '../use_link_props'; const StyledCubeForProcess = styled(CubeForProcess)` position: relative; @@ -44,7 +38,11 @@ export const NodeDetail = memo(function ({ nodeID }: { nodeID: string }) { ); return ( - {processEvent === null ? : } + {processEvent === null ? ( + + ) : ( + + )} ); }); @@ -53,21 +51,22 @@ export const NodeDetail = memo(function ({ nodeID }: { nodeID: string }) { * A description list view of all the Metadata that goes with a particular process event, like: * Created, PID, User/Domain, etc. */ -const NodeDetailView = memo(function NodeDetailView({ +const NodeDetailView = memo(function ({ processEvent, + nodeID, }: { - processEvent: ResolverEvent; + processEvent: SafeResolverEvent; + nodeID: string; }) { - const processName = event.eventName(processEvent); - const entityId = event.entityId(processEvent); + const processName = eventModel.processNameSafeVersion(processEvent); const isProcessTerminated = useSelector((state: ResolverState) => - selectors.isProcessTerminated(state)(entityId) + selectors.isProcessTerminated(state)(nodeID) ); const relatedEventTotal = useSelector((state: ResolverState) => { - return selectors.relatedEventAggregateTotalByEntityId(state)(entityId); + return selectors.relatedEventTotalCount(state)(nodeID); }); const processInfoEntry: EuiDescriptionListProps['listItems'] = useMemo(() => { - const eventTime = event.eventTimestamp(processEvent); + const eventTime = eventModel.eventTimestamp(processEvent); const dateTime = eventTime === undefined ? null : formatDate(eventTime); const createdEntry = { @@ -82,32 +81,32 @@ const NodeDetailView = memo(function NodeDetailView({ const pidEntry = { title: 'process.pid', - description: processPid(processEvent), + description: processPID(processEvent), }; const userEntry = { title: 'user.name', - description: userInfoForProcess(processEvent)?.name, + description: eventModel.userName(processEvent), }; const domainEntry = { title: 'user.domain', - description: userInfoForProcess(processEvent)?.domain, + description: eventModel.userDomain(processEvent), }; const parentPidEntry = { title: 'process.parent.pid', - description: processParentPid(processEvent), + description: eventModel.parentPID(processEvent), }; const md5Entry = { title: 'process.hash.md5', - description: md5HashForProcess(processEvent), + description: eventModel.md5HashForProcess(processEvent), }; const commandLineEntry = { title: 'process.args', - description: argsForProcess(processEvent), + description: eventModel.argsForProcess(processEvent), }; // This is the data in {title, description} form for the EuiDescriptionList to display @@ -134,12 +133,8 @@ const NodeDetailView = memo(function NodeDetailView({ return processDescriptionListData; }, [processEvent]); - const nodesHref = useSelector((state: ResolverState) => - selectors.relativeHref(state)({ panelView: 'nodes' }) - ); - - const nodesLinkNavProps = useNavigateOrReplace({ - search: nodesHref, + const nodesLinkNavProps = useLinkProps({ + panelView: 'nodes', }); const crumbs = useMemo(() => { @@ -162,27 +157,20 @@ const NodeDetailView = memo(function NodeDetailView({ defaultMessage="Details for: {processName}" /> ), - onClick: () => {}, }, ]; }, [processName, nodesLinkNavProps]); const { descriptionText } = useCubeAssets(isProcessTerminated, false); - const nodeDetailHref = useSelector((state: ResolverState) => - selectors.relativeHref(state)({ - panelView: 'nodeEvents', - panelParameters: { nodeID: entityId }, - }) - ); - - const nodeDetailNavProps = useNavigateOrReplace({ - search: nodeDetailHref!, + const nodeDetailNavProps = useLinkProps({ + panelView: 'nodeEvents', + panelParameters: { nodeID }, }); const titleID = useMemo(() => htmlIdGenerator('resolverTable')(), []); return ( <> - + @@ -201,7 +189,7 @@ const NodeDetailView = memo(function NodeDetailView({ - + @@ -26,11 +28,21 @@ export function NodeEvents({ nodeID }: { nodeID: string }) { selectors.relatedEventsStats(state)(nodeID) ); if (processEvent === null || relatedEventsStats === undefined) { - return ; + return ( + + + + ); } else { return ( - + + + ); } @@ -47,120 +59,29 @@ export function NodeEvents({ nodeID }: { nodeID: string }) { * | 2 | Network | * */ -const EventCountsForProcess = memo(function EventCountsForProcess({ - processEvent, +const EventCategoryLinks = memo(function ({ + nodeID, relatedStats, }: { - processEvent: ResolverEvent; + nodeID: string; relatedStats: ResolverNodeStats; }) { interface EventCountsTableView { - name: string; + eventType: string; count: number; } - const relatedEventsState = { stats: relatedStats.events.byCategory }; - const processName = processEvent && event.eventName(processEvent); - const processEntityId = event.entityId(processEvent); - /** - * totalCount: This will reflect the aggregated total by category for all related events - * e.g. [dns,file],[dns,file],[registry] will have an aggregate total of 5. This is to keep the - * total number consistent with the "broken out" totals we see elsewhere in the app. - * E.g. on the rleated list by type, the above would show as: - * 2 dns - * 2 file - * 1 registry - * So it would be extremely disorienting to show the user a "3" above that as a total. - */ - const totalCount = Object.values(relatedStats.events.byCategory).reduce( - (sum, val) => sum + val, - 0 - ); - const eventsString = i18n.translate( - 'xpack.securitySolution.endpoint.resolver.panel.processEventCounts.events', - { - defaultMessage: 'Events', - } - ); - const eventsHref = useSelector((state: ResolverState) => - selectors.relativeHref(state)({ panelView: 'nodes' }) - ); - - const eventLinkNavProps = useNavigateOrReplace({ - search: eventsHref, - }); - - const processDetailHref = useSelector((state: ResolverState) => - selectors.relativeHref(state)({ - panelView: 'nodeDetail', - panelParameters: { nodeID: processEntityId }, - }) - ); - - const processDetailNavProps = useNavigateOrReplace({ - search: processDetailHref, - }); - - const nodeDetailHref = useSelector((state: ResolverState) => - selectors.relativeHref(state)({ - panelView: 'nodeEvents', - panelParameters: { nodeID: processEntityId }, - }) - ); - - const nodeDetailNavProps = useNavigateOrReplace({ - search: nodeDetailHref!, - }); - const crumbs = useMemo(() => { - return [ - { - text: eventsString, - ...eventLinkNavProps, - }, - { - text: processName, - ...processDetailNavProps, - }, - { - text: ( - - ), - ...nodeDetailNavProps, - }, - ]; - }, [ - processName, - totalCount, - eventsString, - eventLinkNavProps, - nodeDetailNavProps, - processDetailNavProps, - ]); const rows = useMemo(() => { - return Object.entries(relatedEventsState.stats).map( + return Object.entries(relatedStats.events.byCategory).map( ([eventType, count]): EventCountsTableView => { return { - name: eventType, + eventType, count, }; } ); - }, [relatedEventsState]); - - const eventDetailHref = useSelector((state: ResolverState) => - selectors.relativeHref(state)({ - panelView: 'eventDetail', - panelParameters: { nodeID: processEntityId, eventType: name, eventID: processEntityId }, - }) - ); + }, [relatedStats.events.byCategory]); - const eventDetailNavProps = useNavigateOrReplace({ - search: eventDetailHref, - }); const columns = useMemo>>( () => [ { @@ -168,29 +89,100 @@ const EventCountsForProcess = memo(function EventCountsForProcess({ name: i18n.translate('xpack.securitySolution.endpoint.resolver.panel.table.row.count', { defaultMessage: 'Count', }), + 'data-test-subj': 'resolver:panel:node-events:event-type-count', width: '20%', sortable: true, }, { - field: 'name', + field: 'eventType', name: i18n.translate('xpack.securitySolution.endpoint.resolver.panel.table.row.eventType', { defaultMessage: 'Event Type', }), width: '80%', sortable: true, - render(name: string) { - return {name}; + render(eventType: string) { + return ( + + {eventType} + + ); }, }, ], - [eventDetailNavProps] + [nodeID] ); + return items={rows} columns={columns} sorting />; +}); + +const NodeEventsBreadcrumbs = memo(function ({ + nodeID, + nodeName, + totalEventCount, +}: { + nodeID: string; + nodeName: React.ReactNode; + totalEventCount: number; +}) { return ( - <> - - - items={rows} columns={columns} sorting /> - + + ), + ...useLinkProps({ + panelView: 'nodeEvents', + panelParameters: { nodeID }, + }), + }, + ]} + /> ); }); -EventCountsForProcess.displayName = 'EventCountsForProcess'; + +const NodeEventsLink = memo( + ({ + nodeID, + eventType, + children, + }: { + nodeID: string; + eventType: string; + children: React.ReactNode; + }) => { + const props = useLinkProps({ + panelView: 'nodeEventsOfType', + panelParameters: { + nodeID, + eventType, + }, + }); + return ( + + {children} + + ); + } +); diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/node_events_of_type.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/node_events_of_type.tsx index afff8d4b75c15..281794ac24d24 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/node_events_of_type.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/node_events_of_type.tsx @@ -4,297 +4,225 @@ * you may not use this file except in compliance with the Elastic License. */ -/* eslint-disable react/display-name */ - -import React, { memo, useMemo, useEffect, Fragment } from 'react'; +import React, { memo, useCallback, Fragment } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiSpacer, EuiText, EuiButtonEmpty, EuiHorizontalRule } from '@elastic/eui'; import { useSelector } from 'react-redux'; import { FormattedMessage } from 'react-intl'; -import styled from 'styled-components'; import { StyledPanel } from '../styles'; -import { formatDate, StyledBreadcrumbs, BoldCode, StyledTime } from './panel_content_utilities'; -import * as event from '../../../../common/endpoint/models/event'; -import { ResolverEvent, ResolverNodeStats } from '../../../../common/endpoint/types'; +import { formatDate, BoldCode, StyledTime } from './panel_content_utilities'; +import { Breadcrumbs } from './breadcrumbs'; +import * as eventModel from '../../../../common/endpoint/models/event'; +import { SafeResolverEvent } from '../../../../common/endpoint/types'; import * as selectors from '../../store/selectors'; -import { useResolverDispatch } from '../use_resolver_dispatch'; -import { RelatedEventLimitWarning } from '../limit_warnings'; import { ResolverState } from '../../types'; -import { useNavigateOrReplace } from '../use_navigate_or_replace'; -import { useRelatedEventDetailNavigation } from '../use_related_event_detail_navigation'; import { PanelLoading } from './panel_loading'; +import { DescriptiveName } from './descriptive_name'; +import { useLinkProps } from '../use_link_props'; /** - * This view presents a list of related events of a given type for a given process. - * It will appear like: - * - * | | - * | :----------------------------------------------------- | - * | **registry deletion** @ *3:32PM..* *HKLM/software...* | - * | **file creation** @ *3:34PM..* *C:/directory/file.exe* | + * Render a list of events that are related to `nodeID` and that have a category of `eventType`. */ - -interface MatchingEventEntry { - formattedDate: string; - eventType: string; - eventCategory: string; - name: { subject: string; descriptor?: string }; - setQueryParams: () => void; -} - -const StyledRelatedLimitWarning = styled(RelatedEventLimitWarning)` - flex-flow: row wrap; - display: block; - align-items: baseline; - margin-top: 1em; - - & .euiCallOutHeader { - display: inline; - margin-right: 0.25em; - } - - & .euiText { - display: inline; - } - - & .euiText p { - display: inline; - } -`; - -const NodeCategoryEntries = memo(function ({ - crumbs, - matchingEventEntries, +export const NodeEventsOfType = memo(function NodeEventsOfType({ + nodeID, eventType, - processEntityId, }: { - crumbs: Array<{ - text: string | JSX.Element | null; - onClick: (event: React.MouseEvent) => void; - href?: string; - }>; - matchingEventEntries: MatchingEventEntry[]; + nodeID: string; eventType: string; - processEntityId: string; }) { - const relatedLookupsByCategory = useSelector(selectors.relatedEventInfoByEntityId); - const lookupsForThisNode = relatedLookupsByCategory(processEntityId); - const shouldShowLimitWarning = lookupsForThisNode?.shouldShowLimitForCategory(eventType); - const numberDisplayed = lookupsForThisNode?.numberActuallyDisplayedForCategory(eventType); - const numberMissing = lookupsForThisNode?.numberNotDisplayedForCategory(eventType); - - return ( - <> - - {shouldShowLimitWarning && typeof numberDisplayed !== 'undefined' && numberMissing ? ( - - ) : null} - - <> - {matchingEventEntries.map((eventView, index) => { - const { subject, descriptor = '' } = eventView.name; - return ( - - - - - - - - - - - - - - {index === matchingEventEntries.length - 1 ? null : } - - ); - })} - - - ); -}); - -export function NodeEventsOfType({ nodeID, eventType }: { nodeID: string; eventType: string }) { const processEvent = useSelector((state: ResolverState) => selectors.processEventForID(state)(nodeID) ); - const relatedEventsStats = useSelector((state: ResolverState) => - selectors.relatedEventsStats(state)(nodeID) + const eventCount = useSelector( + (state: ResolverState) => selectors.relatedEventsStats(state)(nodeID)?.events.total + ); + const eventsInCategoryCount = useSelector( + (state: ResolverState) => + selectors.relatedEventsStats(state)(nodeID)?.events.byCategory[eventType] + ); + const events = useSelector( + useCallback( + (state: ResolverState) => { + return selectors.relatedEventsByCategory(state)(nodeID, eventType); + }, + [eventType, nodeID] + ) ); return ( - + {eventCount === undefined || processEvent === null ? ( + + ) : ( + <> + + + + + )} ); -} +}); -const NodeEventList = memo(function ({ - processEvent, +/** + * Rendered for each event in the list. + */ +const NodeEventsListItem = memo(function ({ + event, + nodeID, eventType, - relatedStats, }: { - processEvent: ResolverEvent | null; + event: SafeResolverEvent; + nodeID: string; eventType: string; - relatedStats: ResolverNodeStats | undefined; }) { - const processName = processEvent && event.eventName(processEvent); - const processEntityId = processEvent ? event.entityId(processEvent) : ''; - const nodesHref = useSelector((state: ResolverState) => - selectors.relativeHref(state)({ panelView: 'nodes' }) - ); - - const nodesLinkNavProps = useNavigateOrReplace({ - search: nodesHref, + const timestamp = eventModel.eventTimestamp(event); + const date = timestamp !== undefined ? formatDate(timestamp) : timestamp; + const linkProps = useLinkProps({ + panelView: 'eventDetail', + panelParameters: { + nodeID, + eventType, + eventID: String(eventModel.eventID(event)), + }, }); - const totalCount = relatedStats - ? Object.values(relatedStats.events.byCategory).reduce((sum, val) => sum + val, 0) - : 0; - const eventsString = i18n.translate( - 'xpack.securitySolution.endpoint.resolver.panel.processEventListByType.events', - { - defaultMessage: 'Events', - } + return ( + <> + + + + + + + + + + + + + ); +}); - const relatedsReadyMap = useSelector(selectors.relatedEventsReady); - const relatedsReady = processEntityId && relatedsReadyMap.get(processEntityId); - - const dispatch = useResolverDispatch(); - - useEffect(() => { - if (typeof relatedsReady === 'undefined') { - dispatch({ - type: 'appDetectedMissingEventData', - payload: processEntityId, - }); - } - }, [relatedsReady, dispatch, processEntityId]); - - const relatedByCategory = useSelector(selectors.relatedEventsByCategory); - const eventsForCurrentCategory = relatedByCategory(processEntityId)(eventType); - const relatedEventDetailNavigation = useRelatedEventDetailNavigation({ - nodeID: processEntityId, - category: eventType, - events: eventsForCurrentCategory, - }); - +/** + * Renders a list of events with a separator in between. + */ +const NodeEventList = memo(function NodeEventList({ + eventType, + events, + nodeID, +}: { + eventType: string; /** - * A list entry will be displayed for each of these + * The events to list. */ - const matchingEventEntries: MatchingEventEntry[] = useMemo(() => { - return eventsForCurrentCategory.map((resolverEvent) => { - const eventTime = event.eventTimestamp(resolverEvent); - const formattedDate = typeof eventTime === 'undefined' ? '' : formatDate(eventTime); - const entityId = event.eventId(resolverEvent); - return { - formattedDate, - eventCategory: `${eventType}`, - eventType: `${event.ecsEventType(resolverEvent)}`, - name: event.descriptiveName(resolverEvent), - setQueryParams: () => relatedEventDetailNavigation(entityId), - }; - }); - }, [eventType, eventsForCurrentCategory, relatedEventDetailNavigation]); - - const nodeDetailHref = useSelector((state: ResolverState) => - selectors.relativeHref(state)({ - panelView: 'nodeDetail', - panelParameters: { nodeID: processEntityId }, - }) + events: SafeResolverEvent[]; + nodeID: string; +}) { + return ( + <> + {events.map((event, index) => ( + + + {index === events.length - 1 ? null : } + + ))} + ); +}); - const nodeDetailNavProps = useNavigateOrReplace({ - search: nodeDetailHref, +/** + * Renders `Breadcrumbs`. + */ +const NodeEventsOfTypeBreadcrumbs = memo(function ({ + nodeName, + eventType, + eventCount, + nodeID, + /** + * The count of events in the category that this list is showing. + */ + eventsInCategoryCount, +}: { + nodeName: React.ReactNode; + eventType: string; + /** + * The events to list. + */ + eventCount: number; + nodeID: string; + /** + * The count of events in the category that this list is showing. + */ + eventsInCategoryCount: number | undefined; +}) { + const nodesLinkNavProps = useLinkProps({ + panelView: 'nodes', }); - const nodeEventsHref = useSelector((state: ResolverState) => - selectors.relativeHref(state)({ - panelView: 'nodeEvents', - panelParameters: { nodeID: processEntityId }, - }) - ); - - const nodeEventsNavProps = useNavigateOrReplace({ - search: nodeEventsHref, + const nodeDetailNavProps = useLinkProps({ + panelView: 'nodeDetail', + panelParameters: { nodeID }, }); - const crumbs = useMemo(() => { - return [ - { - text: eventsString, - ...nodesLinkNavProps, - }, - { - text: processName, - ...nodeDetailNavProps, - }, - { - text: ( - - ), - ...nodeEventsNavProps, - }, - { - text: ( - - ), - onClick: () => {}, - }, - ]; - }, [ - eventType, - eventsString, - matchingEventEntries.length, - processName, - totalCount, - nodeDetailNavProps, - nodesLinkNavProps, - nodeEventsNavProps, - ]); - if (!relatedsReady) { - return ; - } + const nodeEventsNavProps = useLinkProps({ + panelView: 'nodeEvents', + panelParameters: { nodeID }, + }); return ( - + ), + ...nodeEventsNavProps, + }, + { + text: ( + + ), + }, + ]} /> ); }); diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/node_list.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/node_list.tsx index 6113cea4c4edc..8fc6e7cc66c79 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/node_list.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/node_list.tsx @@ -4,9 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable @elastic/eui/href-or-on-click */ + +/* eslint-disable no-duplicate-imports */ + +import { useDispatch } from 'react-redux'; + /* eslint-disable react/display-name */ -import React, { memo, useMemo } from 'react'; +import React, { memo, useMemo, useCallback, useContext } from 'react'; import { EuiBasicTableColumn, EuiBadge, @@ -16,71 +22,31 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useSelector } from 'react-redux'; -import styled from 'styled-components'; +import { SideEffectContext } from '../side_effect_context'; import { StyledPanel } from '../styles'; -import * as event from '../../../../common/endpoint/models/event'; +import { + StyledLabelTitle, + StyledAnalyzedEvent, + StyledLabelContainer, + StyledButtonTextContainer, +} from './styles'; +import * as eventModel from '../../../../common/endpoint/models/event'; import * as selectors from '../../store/selectors'; -import { formatter, StyledBreadcrumbs } from './panel_content_utilities'; +import { formatter } from './panel_content_utilities'; +import { Breadcrumbs } from './breadcrumbs'; import { CubeForProcess } from './cube_for_process'; -import { SafeResolverEvent } from '../../../../common/endpoint/types'; import { LimitWarning } from '../limit_warnings'; import { ResolverState } from '../../types'; -import { useNavigateOrReplace } from '../use_navigate_or_replace'; +import { useLinkProps } from '../use_link_props'; import { useColors } from '../use_colors'; - -const StyledLimitWarning = styled(LimitWarning)` - flex-flow: row wrap; - display: block; - align-items: baseline; - margin-top: 1em; - - & .euiCallOutHeader { - display: inline; - margin-right: 0.25em; - } - - & .euiText { - display: inline; - } - - & .euiText p { - display: inline; - } -`; - -const StyledButtonTextContainer = styled.div` - align-items: center; - display: flex; - flex-direction: row; -`; - -const StyledAnalyzedEvent = styled.div` - color: ${(props) => props.color}; - font-size: 10.5px; - font-weight: 700; -`; - -const StyledLabelTitle = styled.div``; - -const StyledLabelContainer = styled.div` - display: inline-block; - flex: 3; - min-width: 0; - - ${StyledAnalyzedEvent}, - ${StyledLabelTitle} { - overflow: hidden; - text-align: left; - text-overflow: ellipsis; - white-space: nowrap; - } -`; +import { SafeResolverEvent } from '../../../../common/endpoint/types'; +import { ResolverAction } from '../../store/actions'; interface ProcessTableView { name?: string; timestamp?: Date; + nodeID: string; event: SafeResolverEvent; - href: string | undefined; } /** @@ -99,8 +65,8 @@ export const NodeList = memo(() => { ), sortable: true, truncateText: true, - render(name: string, item: ProcessTableView) { - return ; + render(name: string | undefined, item: ProcessTableView) { + return ; }, }, { @@ -132,42 +98,26 @@ export const NodeList = memo(() => { [] ); - const { processNodePositions } = useSelector(selectors.layout); - const nodeHrefs: Map = useSelector( - (state: ResolverState) => { - const relativeHref = selectors.relativeHref(state); - return new Map( - [...processNodePositions.keys()].map((processEvent) => { - const nodeID = event.entityIDSafeVersion(processEvent); - if (nodeID === undefined) { - return [processEvent, null]; - } - return [ - processEvent, - relativeHref({ - panelView: 'nodeDetail', - panelParameters: { - nodeID, - }, - }), - ]; - }) - ); - } - ); - const processTableView: ProcessTableView[] = useMemo( - () => - [...processNodePositions.keys()].map((processEvent) => { - const name = event.processNameSafeVersion(processEvent); - return { - name, - timestamp: event.timestampAsDateSafeVersion(processEvent), - event: processEvent, - href: nodeHrefs.get(processEvent) ?? undefined, - }; - }), - [processNodePositions, nodeHrefs] + const processTableView: ProcessTableView[] = useSelector( + useCallback((state: ResolverState) => { + const { processNodePositions } = selectors.layout(state); + const view: ProcessTableView[] = []; + for (const processEvent of processNodePositions.keys()) { + const name = eventModel.processNameSafeVersion(processEvent); + const nodeID = eventModel.entityIDSafeVersion(processEvent); + if (nodeID !== undefined) { + view.push({ + name, + timestamp: eventModel.timestampAsDateSafeVersion(processEvent), + nodeID, + event: processEvent, + }); + } + } + return view; + }, []) ); + const numberOfProcesses = processTableView.length; const crumbs = useMemo(() => { @@ -176,7 +126,6 @@ export const NodeList = memo(() => { text: i18n.translate('xpack.securitySolution.resolver.panel.nodeList.title', { defaultMessage: 'All Process Events', }), - onClick: () => {}, }, ]; }, []); @@ -187,8 +136,8 @@ export const NodeList = memo(() => { const rowProps = useMemo(() => ({ 'data-test-subj': 'resolver:node-list:item' }), []); return ( - - {showWarning && } + + {showWarning && } rowProps={rowProps} @@ -201,16 +150,40 @@ export const NodeList = memo(() => { ); }); -function NodeDetailLink({ name, item }: { name: string; item: ProcessTableView }) { - const entityID = event.entityIDSafeVersion(item.event); - const originID = useSelector(selectors.originID); - const isOrigin = originID === entityID; +function NodeDetailLink({ + name, + nodeID, + event, +}: { + name?: string; + nodeID: string; + event: SafeResolverEvent; +}) { + const isOrigin = useSelector((state: ResolverState) => { + return selectors.originID(state) === nodeID; + }); const isTerminated = useSelector((state: ResolverState) => - entityID === undefined ? false : selectors.isProcessTerminated(state)(entityID) + nodeID === undefined ? false : selectors.isProcessTerminated(state)(nodeID) ); const { descriptionText } = useColors(); + const linkProps = useLinkProps({ panelView: 'nodeDetail', panelParameters: { nodeID } }); + const dispatch: (action: ResolverAction) => void = useDispatch(); + const { timestamp } = useContext(SideEffectContext); + const handleOnClick = useCallback( + (mouseEvent: React.MouseEvent) => { + linkProps.onClick(mouseEvent); + dispatch({ + type: 'userBroughtProcessIntoView', + payload: { + process: event, + time: timestamp(), + }, + }); + }, + [timestamp, linkProps, dispatch, event] + ); return ( - + {name === '' ? ( {i18n.translate( diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_error.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_error.tsx index 3b10a8db2bf12..199758145f117 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_error.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_error.tsx @@ -7,11 +7,8 @@ import { i18n } from '@kbn/i18n'; import { EuiSpacer, EuiText, EuiButtonEmpty } from '@elastic/eui'; import React, { memo, useMemo } from 'react'; -import { useSelector } from 'react-redux'; -import { useNavigateOrReplace } from '../use_navigate_or_replace'; -import * as selectors from '../../store/selectors'; -import { ResolverState } from '../../types'; -import { StyledBreadcrumbs } from './panel_content_utilities'; +import { Breadcrumbs } from './breadcrumbs'; +import { useLinkProps } from '../use_link_props'; /** * Display an error in the panel when something goes wrong and give the user a way to "retreat" back to a default state. @@ -24,12 +21,10 @@ export const PanelContentError = memo(function ({ }: { translatedErrorMessage: string; }) { - const nodesHref = useSelector((state: ResolverState) => - selectors.relativeHref(state)({ panelView: 'nodes' }) - ); - const nodesLinkNavProps = useNavigateOrReplace({ - search: nodesHref, + const nodesLinkNavProps = useLinkProps({ + panelView: 'nodes', }); + const crumbs = useMemo(() => { return [ { @@ -42,13 +37,12 @@ export const PanelContentError = memo(function ({ text: i18n.translate('xpack.securitySolution.endpoint.resolver.panel.error.error', { defaultMessage: 'Error', }), - onClick: () => {}, }, ]; }, [nodesLinkNavProps]); return ( <> - + {translatedErrorMessage} @@ -60,4 +54,3 @@ export const PanelContentError = memo(function ({ ); }); -PanelContentError.displayName = 'TableServiceError'; diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx index a7d76277c6ab1..5ca34b33b2396 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx @@ -7,10 +7,9 @@ /* eslint-disable react/display-name */ import { i18n } from '@kbn/i18n'; -import { EuiBreadcrumbs, EuiCode, EuiBetaBadge } from '@elastic/eui'; +import { EuiCode } from '@elastic/eui'; import styled from 'styled-components'; import React, { memo } from 'react'; -import { useColors } from '../use_colors'; /** * A bold version of EuiCode to display certain titles with @@ -21,30 +20,6 @@ export const BoldCode = styled(EuiCode)` } `; -const BetaHeader = styled(`header`)` - margin-bottom: 1em; -`; - -const ThemedBreadcrumbs = styled(EuiBreadcrumbs)<{ background: string; text: string }>` - &.euiBreadcrumbs { - background-color: ${(props) => props.background}; - color: ${(props) => props.text}; - padding: 1em; - border-radius: 5px; - } - - & .euiBreadcrumbSeparator { - background: ${(props) => props.text}; - } -`; - -const betaBadgeLabel = i18n.translate( - 'xpack.securitySolution.enpdoint.resolver.panelutils.betaBadgeLabel', - { - defaultMessage: 'BETA', - } -); - /** * A component that renders an element with breaking opportunities (``s) * spliced into text children at word boundaries. @@ -85,31 +60,6 @@ export const StyledTime = memo(styled('time')` text-align: start; `); -type Breadcrumbs = Parameters[0]['breadcrumbs']; -/** - * Breadcrumb menu with adjustments per direction from UX team - */ -export const StyledBreadcrumbs = memo(function StyledBreadcrumbs({ - breadcrumbs, -}: { - breadcrumbs: Breadcrumbs; -}) { - const { resolverBreadcrumbBackground, resolverEdgeText } = useColors(); - return ( - <> - - - - - - ); -}); - /** * Long formatter (to second) for DateTime */ @@ -122,12 +72,6 @@ export const formatter = new Intl.DateTimeFormat(i18n.getLocale(), { second: '2-digit', }); -const invalidDateText = i18n.translate( - 'xpack.securitySolution.enpdoint.resolver.panelutils.invaliddate', - { - defaultMessage: 'Invalid Date', - } -); /** * @returns {string} A nicely formatted string for a date */ @@ -140,6 +84,8 @@ export function formatDate( if (isFinite(date.getTime())) { return formatter.format(date); } else { - return invalidDateText; + return i18n.translate('xpack.securitySolution.enpdoint.resolver.panelutils.invaliddate', { + defaultMessage: 'Invalid Date', + }); } } diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_loading.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_loading.tsx index 864990e4d96ab..2de0bf5d320ea 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_loading.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_loading.tsx @@ -5,13 +5,10 @@ */ import React, { useMemo } from 'react'; -import { useSelector } from 'react-redux'; import { i18n } from '@kbn/i18n'; import { EuiSpacer, EuiTitle } from '@elastic/eui'; -import * as selectors from '../../store/selectors'; -import { StyledBreadcrumbs } from './panel_content_utilities'; -import { useNavigateOrReplace } from '../use_navigate_or_replace'; -import { ResolverState } from '../../types'; +import { Breadcrumbs } from './breadcrumbs'; +import { useLinkProps } from '../use_link_props'; export function PanelLoading() { const waitingString = i18n.translate( @@ -26,11 +23,8 @@ export function PanelLoading() { defaultMessage: 'Events', } ); - const nodesHref = useSelector((state: ResolverState) => - selectors.relativeHref(state)({ panelView: 'nodes' }) - ); - const nodesLinkNavProps = useNavigateOrReplace({ - search: nodesHref, + const nodesLinkNavProps = useLinkProps({ + panelView: 'nodes', }); const waitCrumbs = useMemo(() => { return [ @@ -42,7 +36,7 @@ export function PanelLoading() { }, [nodesLinkNavProps, eventsString]); return ( <> - +

{waitingString}

diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/styles.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/styles.tsx index c5d5ae53a5580..03826dd38397b 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/styles.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/styles.tsx @@ -3,15 +3,89 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + +/* eslint-disable no-duplicate-imports */ + +import { EuiBreadcrumbs } from '@elastic/eui'; + import styled from 'styled-components'; import { EuiDescriptionList } from '@elastic/eui'; +/** + * Used by the nodeDetail view to show attributes of the related events. + */ export const StyledDescriptionList = styled(EuiDescriptionList)` &.euiDescriptionList.euiDescriptionList--column dt.euiDescriptionList__title.desc-title { max-width: 10em; } `; +/** + * Used by the nodeDetail view for the label of the node. + */ export const StyledTitle = styled('h4')` overflow-wrap: break-word; `; + +/** + * Used for a 'BETA' badge in the breadcrumbs of each panel. + */ +export const BetaHeader = styled(`header`)` + margin-bottom: 1em; +`; + +/** + * Styled version of EuiBreadcrumbs that is used by the breadcrumbs in each panel. + */ +export const ThemedBreadcrumbs = styled(EuiBreadcrumbs)<{ background: string; text: string }>` + &.euiBreadcrumbs { + background-color: ${(props) => props.background}; + color: ${(props) => props.text}; + padding: 1em; + border-radius: 5px; + } + + & .euiBreadcrumbSeparator { + background: ${(props) => props.text}; + } +`; + +/** + * Used in the links to nodes on the node list panel. + */ +export const StyledButtonTextContainer = styled.div` + align-items: center; + display: flex; + flex-direction: row; +`; + +/** + * Used in the node list panel to call out the event that is represented by the databaseDocumentID. + */ +export const StyledAnalyzedEvent = styled.div` + color: ${(props) => props.color}; + font-size: 10.5px; + font-weight: 700; +`; + +/** + * Used to style the node name in the node list panel view. + */ +export const StyledLabelTitle = styled.div``; + +/** + * Used by the node list view. Wraps the title of the node and the 'Analyzed event' marker. + */ +export const StyledLabelContainer = styled.div` + display: inline-block; + flex: 3; + min-width: 0; + + ${StyledAnalyzedEvent}, + ${StyledLabelTitle} { + overflow: hidden; + text-align: left; + text-overflow: ellipsis; + white-space: nowrap; + } +`; diff --git a/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx b/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx index 65ec395080f86..4d647760edb9c 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx @@ -12,15 +12,15 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { NodeSubMenu } from './submenu'; import { applyMatrix3 } from '../models/vector2'; import { Vector2, Matrix3, ResolverState } from '../types'; -import { ResolverEvent, SafeResolverEvent } from '../../../common/endpoint/types'; +import { SafeResolverEvent } from '../../../common/endpoint/types'; import { useResolverDispatch } from './use_resolver_dispatch'; import * as eventModel from '../../../common/endpoint/models/event'; import * as selectors from '../store/selectors'; -import { useNavigateOrReplace } from './use_navigate_or_replace'; import { fontSize } from './font_size'; import { useCubeAssets } from './use_cube_assets'; import { useSymbolIDs } from './use_symbol_ids'; import { useColors } from './use_colors'; +import { useLinkProps } from './use_link_props'; interface StyledActionsContainer { readonly color: string; @@ -192,7 +192,6 @@ const UnstyledProcessEventDot = React.memo( /** * Type in non-SVG components scales as follows: - * (These values were adjusted to match the proportions in the comps provided by UX/Design) * 18.75 : The smallest readable font size at which labels/descriptions can be read. Font size will not scale below this. * 12.5 : A 'slope' at which the font size will scale w.r.t. to zoom level otherwise */ @@ -239,15 +238,10 @@ const UnstyledProcessEventDot = React.memo( const isOrigin = nodeID === originID; const dispatch = useResolverDispatch(); - const processDetailHref = useSelector((state: ResolverState) => - selectors.relativeHref(state)({ - panelView: 'nodeDetail', - panelParameters: { nodeID }, - }) - ); - const processDetailNavProps = useNavigateOrReplace({ - search: processDetailHref, + const processDetailNavProps = useLinkProps({ + panelView: 'nodeDetail', + panelParameters: { nodeID }, }); const handleFocus = useCallback(() => { @@ -272,7 +266,7 @@ const UnstyledProcessEventDot = React.memo( ); const grandTotal: number | null = useSelector((state: ResolverState) => - selectors.relatedEventTotalForProcess(state)(event as ResolverEvent) + selectors.relatedEventTotalForProcess(state)(event) ); /* eslint-disable jsx-a11y/click-events-have-key-events */ @@ -376,12 +370,13 @@ const UnstyledProcessEventDot = React.memo( backgroundColor={colorMap.resolverBackground} color={colorMap.descriptionText} isDisplaying={isShowingDescriptionText} + data-test-subj="resolver:node:description" > diff --git a/x-pack/plugins/security_solution/public/resolver/view/styles.tsx b/x-pack/plugins/security_solution/public/resolver/view/styles.tsx index fb4d4d289d254..7def5d3362d4f 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/styles.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/styles.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiPanel } from '@elastic/eui'; +import { EuiPanel, EuiCallOut } from '@elastic/eui'; import styled from 'styled-components'; @@ -62,3 +62,26 @@ export const GraphContainer = styled.div` flex-grow: 1; contain: layout; `; + +/** + * See `RelatedEventLimitWarning` + */ +export const LimitWarningsEuiCallOut = styled(EuiCallOut)` + flex-flow: row wrap; + display: block; + align-items: baseline; + margin-top: 1em; + + & .euiCallOutHeader { + display: inline; + margin-right: 0.25em; + } + + & .euiText { + display: inline; + } + + & .euiText p { + display: inline; + } +`; diff --git a/x-pack/plugins/security_solution/public/resolver/view/use_camera.test.tsx b/x-pack/plugins/security_solution/public/resolver/view/use_camera.test.tsx index 495cd238d22fc..5406b444cee56 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/use_camera.test.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/use_camera.test.tsx @@ -11,7 +11,7 @@ import { useCamera, useAutoUpdatingClientRect } from './use_camera'; import { Provider } from 'react-redux'; import * as selectors from '../store/selectors'; import { Matrix3, ResolverStore, SideEffectSimulator } from '../types'; -import { ResolverEvent } from '../../../common/endpoint/types'; +import { SafeResolverEvent } from '../../../common/endpoint/types'; import { SideEffectContext } from './side_effect_context'; import { applyMatrix3 } from '../models/vector2'; import { sideEffectSimulatorFactory } from './side_effect_simulator_factory'; @@ -33,7 +33,7 @@ describe('useCamera on an unpainted element', () => { beforeEach(async () => { store = createStore(resolverReducer); - const Test = function Test() { + const Test = function () { const camera = useCamera(); const { ref, onMouseDown } = camera; projectionMatrix = camera.projectionMatrix; @@ -160,9 +160,9 @@ describe('useCamera on an unpainted element', () => { expect(simulator.mock.requestAnimationFrame).not.toHaveBeenCalled(); }); describe('when the camera begins animation', () => { - let process: ResolverEvent; + let process: SafeResolverEvent; beforeEach(() => { - const events: ResolverEvent[] = []; + const events: SafeResolverEvent[] = []; const numberOfEvents: number = 10; for (let index = 0; index < numberOfEvents; index++) { @@ -190,9 +190,9 @@ describe('useCamera on an unpainted element', () => { } else { throw new Error('failed to create tree'); } - const processes: ResolverEvent[] = [ + const processes: SafeResolverEvent[] = [ ...selectors.layout(store.getState()).processNodePositions.keys(), - ] as ResolverEvent[]; + ]; process = processes[processes.length - 1]; if (!process) { throw new Error('missing the process to bring into view'); diff --git a/x-pack/plugins/security_solution/public/resolver/view/use_link_props.ts b/x-pack/plugins/security_solution/public/resolver/view/use_link_props.ts new file mode 100644 index 0000000000000..5645edec7e1ca --- /dev/null +++ b/x-pack/plugins/security_solution/public/resolver/view/use_link_props.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useSelector } from 'react-redux'; +import { MouseEventHandler } from 'react'; +import { useNavigateOrReplace } from './use_navigate_or_replace'; + +import * as selectors from '../store/selectors'; +import { PanelViewAndParameters, ResolverState } from '../types'; + +type EventHandlerCallback = MouseEventHandler; + +/** + * Get an `onClick` function and an `href` string. Use these as props for `` elements. + * `onClick` will use navigate to the `panelViewAndParameters` using `history.push`. + * the `href` points to `panelViewAndParameters`. + * Existing `search` parameters are maintained. + */ +export function useLinkProps( + panelViewAndParameters: PanelViewAndParameters +): { href: string; onClick: EventHandlerCallback } { + const search = useSelector((state: ResolverState) => + selectors.relativeHref(state)(panelViewAndParameters) + ); + + return useNavigateOrReplace({ + search, + }); +} diff --git a/x-pack/plugins/security_solution/public/resolver/view/use_related_event_by_category_navigation.ts b/x-pack/plugins/security_solution/public/resolver/view/use_related_event_by_category_navigation.ts index f994350132c35..6810837ae031a 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/use_related_event_by_category_navigation.ts +++ b/x-pack/plugins/security_solution/public/resolver/view/use_related_event_by_category_navigation.ts @@ -12,7 +12,7 @@ import * as selectors from '../store/selectors'; /** * A hook that takes a nodeID and a record of categories, and returns a function that * navigates to the proper url when called with a category. - * @deprecated + * @deprecated See `useLinkProps` */ export function useRelatedEventByCategoryNavigation({ nodeID, diff --git a/x-pack/plugins/security_solution/public/resolver/view/use_related_event_detail_navigation.ts b/x-pack/plugins/security_solution/public/resolver/view/use_related_event_detail_navigation.ts deleted file mode 100644 index 9fc74a7567c47..0000000000000 --- a/x-pack/plugins/security_solution/public/resolver/view/use_related_event_detail_navigation.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; - * you may not use this file except in compliance with the Elastic License. - */ -import { useCallback } from 'react'; -import { useSelector } from 'react-redux'; -import { useHistory } from 'react-router-dom'; -import { ResolverState } from '../types'; -import { ResolverEvent } from '../../../common/endpoint/types'; -import * as selectors from '../store/selectors'; - -/** - * @deprecated - */ -export function useRelatedEventDetailNavigation({ - nodeID, - category, - events, -}: { - nodeID: string; - category: string; - events: ResolverEvent[]; -}) { - const relatedEventDetailUrls = useSelector((state: ResolverState) => - selectors.relatedEventDetailHrefs(state)(category, nodeID, events) - ); - const history = useHistory(); - return useCallback( - (entityID: string | number | undefined) => { - if (entityID !== undefined) { - const urlForEntityID = relatedEventDetailUrls.get(String(entityID)); - if (urlForEntityID !== null && urlForEntityID !== undefined) { - return history.replace({ search: urlForEntityID }); - } - } - }, - [history, relatedEventDetailUrls] - ); -} diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/button/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/button/index.tsx index c0a59fd07e348..954ae0b6a0d40 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/button/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/button/index.tsx @@ -10,12 +10,13 @@ import { rgba } from 'polished'; import React, { useMemo } from 'react'; import styled from 'styled-components'; -import { useWithSource } from '../../../../common/containers/source'; import { IS_DRAGGING_CLASS_NAME } from '../../../../common/components/drag_and_drop/helpers'; import { DataProvider } from '../../timeline/data_providers/data_provider'; import { flattenIntoAndGroups } from '../../timeline/data_providers/helpers'; import { DataProviders } from '../../timeline/data_providers'; import * as i18n from './translations'; +import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; export const FLYOUT_BUTTON_CLASS_NAME = 'timeline-flyout-button'; @@ -85,7 +86,7 @@ interface FlyoutButtonProps { export const FlyoutButton = React.memo( ({ onOpen, show, dataProviders, timelineId }) => { const badgeCount = useMemo(() => getBadgeCount(dataProviders), [dataProviders]); - const { browserFields } = useWithSource(); + const { browserFields } = useSourcererScope(SourcererScopeName.timeline); if (!show) { return null; diff --git a/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.test.tsx index fe0f0c8f8b91f..3814bc01bd9b1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.test.tsx @@ -33,33 +33,33 @@ describe('useTimelineManager', () => { expect(isStringifiedComparisonEqual(uninitializedTimeline, timelineDefaults)).toBeTruthy(); }); }); - - it('getIndexToAddById', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useTimelineManager() - ); - await waitForNextUpdate(); - const data = result.current.getIndexToAddById(testId); - expect(data).toEqual(timelineDefaults.indexToAdd); - }); - }); - - it('setIndexToAdd', async () => { - await act(async () => { - const indexToAddArgs = { id: testId, indexToAdd: ['example'] }; - const { result, waitForNextUpdate } = renderHook(() => - useTimelineManager() - ); - await waitForNextUpdate(); - result.current.initializeTimeline({ - id: testId, - }); - result.current.setIndexToAdd(indexToAddArgs); - const data = result.current.getIndexToAddById(testId); - expect(data).toEqual(indexToAddArgs.indexToAdd); - }); - }); + // TO DO sourcerer + // it('getIndexToAddById', async () => { + // await act(async () => { + // const { result, waitForNextUpdate } = renderHook(() => + // useTimelineManager() + // ); + // await waitForNextUpdate(); + // const data = result.current.getIndexToAddById(testId); + // expect(data).toEqual(timelineDefaults.indexToAdd); + // }); + // }); + // + // it('setIndexToAdd', async () => { + // await act(async () => { + // const indexToAddArgs = { id: testId, indexToAdd: ['example'] }; + // const { result, waitForNextUpdate } = renderHook(() => + // useTimelineManager() + // ); + // await waitForNextUpdate(); + // result.current.initializeTimeline({ + // id: testId, + // }); + // result.current.setIndexToAdd(indexToAddArgs); + // const data = result.current.getIndexToAddById(testId); + // expect(data).toEqual(indexToAddArgs.indexToAdd); + // }); + // }); it('setIsTimelineLoading', async () => { await act(async () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx index f82158fe65c11..4e1e877ec5b88 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx @@ -20,7 +20,6 @@ interface ManageTimelineInit { filterManager?: FilterManager; footerText?: string; id: string; - indexToAdd?: string[] | null; loadingText?: string; selectAll?: boolean; queryFields?: string[]; @@ -34,7 +33,6 @@ interface ManageTimeline { filterManager?: FilterManager; footerText: string; id: string; - indexToAdd: string[] | null; isLoading: boolean; loadingText: string; queryFields: string[]; @@ -58,11 +56,6 @@ type ActionManageTimeline = id: string; payload: boolean; } - | { - type: 'SET_INDEX_TO_ADD'; - id: string; - payload: string[]; - } | { type: 'SET_SELECT_ALL'; id: string; @@ -70,7 +63,6 @@ type ActionManageTimeline = }; export const getTimelineDefaults = (id: string) => ({ - indexToAdd: null, defaultModel: timelineDefaultModel, loadingText: i18n.LOADING_EVENTS, footerText: i18nF.TOTAL_COUNT_OF_EVENTS, @@ -96,14 +88,6 @@ const reducerManageTimeline = ( ...action.payload, }, } as ManageTimelineById; - case 'SET_INDEX_TO_ADD': - return { - ...state, - [action.id]: { - ...state[action.id], - indexToAdd: action.payload, - }, - } as ManageTimelineById; case 'SET_SELECT_ALL': return { ...state, @@ -127,12 +111,10 @@ const reducerManageTimeline = ( }; export interface UseTimelineManager { - getIndexToAddById: (id: string) => string[] | null; getManageTimelineById: (id: string) => ManageTimeline; getTimelineFilterManager: (id: string) => FilterManager | undefined; initializeTimeline: (newTimeline: ManageTimelineInit) => void; isManagedTimeline: (id: string) => boolean; - setIndexToAdd: (indexToAddArgs: { id: string; indexToAdd: string[] }) => void; setIsTimelineLoading: (isLoadingArgs: { id: string; isLoading: boolean }) => void; setSelectAll: (selectAllArgs: { id: string; selectAll: boolean }) => void; } @@ -163,14 +145,6 @@ export const useTimelineManager = ( [] ); - const setIndexToAdd = useCallback(({ id, indexToAdd }: { id: string; indexToAdd: string[] }) => { - dispatch({ - type: 'SET_INDEX_TO_ADD', - id, - payload: indexToAdd, - }); - }, []); - const setSelectAll = useCallback(({ id, selectAll }: { id: string; selectAll: boolean }) => { dispatch({ type: 'SET_SELECT_ALL', @@ -193,36 +167,23 @@ export const useTimelineManager = ( }, [initializeTimeline, state] ); - const getIndexToAddById = useCallback( - (id: string): string[] | null => { - if (state[id] != null) { - return state[id].indexToAdd; - } - return getTimelineDefaults(id).indexToAdd; - }, - [state] - ); const isManagedTimeline = useCallback((id: string): boolean => state[id] != null, [state]); return { - getIndexToAddById, getManageTimelineById, getTimelineFilterManager, initializeTimeline, isManagedTimeline, - setIndexToAdd, setIsTimelineLoading, setSelectAll, }; }; const init = { - getIndexToAddById: (id: string) => null, getManageTimelineById: (id: string) => getTimelineDefaults(id), getTimelineFilterManager: () => undefined, initializeTimeline: () => noop, isManagedTimeline: () => false, - setIndexToAdd: () => undefined, setIsTimelineLoading: () => noop, setSelectAll: () => noop, }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts index ed44fc14e3efa..c89114ec77138 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts @@ -273,6 +273,7 @@ describe('helpers', () => { highlightedDropAndProviderId: '', historyIds: [], id: 'savedObject-1', + indexNames: [], isFavorite: false, isLive: false, isSelectAllChecked: false, @@ -371,6 +372,7 @@ describe('helpers', () => { highlightedDropAndProviderId: '', historyIds: [], id: 'savedObject-1', + indexNames: [], isFavorite: false, isLive: false, isSelectAllChecked: false, @@ -469,6 +471,7 @@ describe('helpers', () => { highlightedDropAndProviderId: '', historyIds: [], id: 'savedObject-1', + indexNames: [], isFavorite: false, isLive: false, isSelectAllChecked: false, @@ -564,6 +567,7 @@ describe('helpers', () => { filters: [], highlightedDropAndProviderId: '', historyIds: [], + indexNames: [], id: 'savedObject-1', isFavorite: false, isLive: false, @@ -699,6 +703,7 @@ describe('helpers', () => { filters: [], highlightedDropAndProviderId: '', historyIds: [], + indexNames: [], isFavorite: false, isLive: false, isSelectAllChecked: false, @@ -865,6 +870,7 @@ describe('helpers', () => { ], highlightedDropAndProviderId: '', historyIds: [], + indexNames: [], isFavorite: false, isLive: false, isSelectAllChecked: false, diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts index b6b6148340a4a..c89740f667b29 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts @@ -56,6 +56,8 @@ import { OpenTimelineResult, UpdateTimeline, DispatchUpdateTimeline } from './ty import { createNote } from '../notes/helpers'; import { IS_OPERATOR } from '../timeline/data_providers/data_provider'; import { normalizeTimeRange } from '../../../common/components/url_state/normalize_time_range'; +import { sourcererActions } from '../../../common/store/sourcerer'; +import { SourcererScopeName } from '../../../common/store/sourcerer/model'; export const OPEN_TIMELINE_CLASS_NAME = 'open-timeline'; @@ -375,6 +377,13 @@ export const dispatchUpdateTimeline = (dispatch: Dispatch): DispatchUpdateTimeli to, ruleNote, }: UpdateTimeline): (() => void) => () => { + dispatch( + sourcererActions.setSelectedIndexPatterns({ + id: SourcererScopeName.timeline, + selectedPatterns: timeline.indexNames, + eventType: timeline.eventType, + }) + ); dispatch(dispatchSetTimelineRangeDatePicker({ from, to })); dispatch(dispatchAddTimeline({ id, timeline, savedTimeline: duplicate })); if ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx index f681043a9047d..dc824a8eb6272 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx @@ -5,12 +5,12 @@ */ import ApolloClient from 'apollo-client'; -import React, { useEffect, useState, useCallback } from 'react'; -import { connect, ConnectedProps } from 'react-redux'; +import React, { useEffect, useState, useCallback, useMemo } from 'react'; +import { connect, ConnectedProps, shallowEqual, useSelector } from 'react-redux'; import { Dispatch } from 'redux'; import { DeleteTimelineMutation, SortFieldTimeline, Direction } from '../../../graphql/types'; -import { State } from '../../../common/store'; +import { sourcererSelectors, State } from '../../../common/store'; import { TimelineId } from '../../../../common/types/timeline'; import { ColumnHeaderOptions, TimelineModel } from '../../../timelines/store/timeline/model'; import { timelineSelectors } from '../../../timelines/store/timeline'; @@ -110,6 +110,15 @@ export const StatefulOpenTimelineComponent = React.memo( /** The requested field to sort on */ const [sortField, setSortField] = useState(DEFAULT_SORT_FIELD); + const existingIndexNamesSelector = useMemo( + () => sourcererSelectors.getAllExistingIndexNamesSelector(), + [] + ); + const existingIndexNames = useSelector( + existingIndexNamesSelector, + shallowEqual + ); + const { customTemplateTimelineCount, defaultTimelineCount, @@ -193,7 +202,12 @@ export const StatefulOpenTimelineComponent = React.memo( const deleteTimelines: DeleteTimelines = useCallback( async (timelineIds: string[]) => { if (timelineIds.includes(timeline.savedObjectId || '')) { - createNewTimeline({ id: TimelineId.active, columns: defaultHeaders, show: false }); + createNewTimeline({ + id: TimelineId.active, + columns: defaultHeaders, + indexNames: existingIndexNames, + show: false, + }); } await apolloClient.mutate< @@ -206,7 +220,7 @@ export const StatefulOpenTimelineComponent = React.memo( }); refetch(); }, - [apolloClient, createNewTimeline, refetch, timeline] + [apolloClient, createNewTimeline, existingIndexNames, refetch, timeline] ); const onDeleteOneTimeline: OnDeleteOneTimeline = useCallback( @@ -382,12 +396,14 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ createNewTimeline: ({ id, columns, + indexNames, show, }: { id: string; columns: ColumnHeaderOptions[]; + indexNames: string[]; show?: boolean; - }) => dispatch(dispatchCreateNewTimeline({ id, columns, show })), + }) => dispatch(dispatchCreateNewTimeline({ id, columns, indexNames, show })), updateIsLoading: ({ id, isLoading }: { id: string; isLoading: boolean }) => dispatch(dispatchUpdateIsLoading({ id, isLoading })), updateTimeline: dispatchUpdateTimeline(dispatch), diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/__snapshots__/timeline.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/__snapshots__/timeline.test.tsx.snap index d76ddace40a5a..18a648f2abfaa 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/__snapshots__/timeline.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/__snapshots__/timeline.test.tsx.snap @@ -806,9 +806,15 @@ In other use cases the message field can be used to concatenate different values } docValueFields={Array []} end="2018-03-24T03:33:52.253Z" - eventType="raw" filters={Array []} - id="foo" + id="test" + indexNames={ + Array [ + "filebeat-*", + "auditbeat-*", + "packetbeat-*", + ] + } indexPattern={ Object { "fields": Array [ @@ -900,9 +906,7 @@ In other use cases the message field can be used to concatenate different values "title": "filebeat-*,auditbeat-*,packetbeat-*", } } - indexToAdd={Array []} isLive={false} - isLoadingSource={false} isSaving={false} itemsPerPage={5} itemsPerPageOptions={ @@ -914,7 +918,7 @@ In other use cases the message field can be used to concatenate different values } kqlMode="search" kqlQueryExpression="" - loadingIndexName={false} + loadingSourcerer={false} onChangeItemsPerPage={[MockFunction]} onClose={[MockFunction]} onDataProviderEdited={[MockFunction]} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx index ea938be91abd1..6e802053ab29f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx @@ -114,7 +114,7 @@ export const ColumnHeadersComponent = ({ timelineId, toggleColumn, }: Props) => { - const [draggingIndex, setDraggingIndex] = useState(null); + const [draggingIndex, setDraggingIndex] = useState(null); const { timelineFullScreen, setTimelineFullScreen, @@ -145,9 +145,7 @@ export const ColumnHeadersComponent = ({ const renderClone: DraggableChildrenFn = useCallback( (dragProvided, _dragSnapshot, rubric) => { - // TODO: Remove after github.com/DefinitelyTyped/DefinitelyTyped/pull/43057 is merged - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const index = (rubric as any).source.index; + const index = rubric.source.index; const header = columnHeaders[index]; const onMount = () => setDraggingIndex(index); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx index 824a37f72ba59..cf9fbfaf19326 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx @@ -11,12 +11,15 @@ import { useDispatch } from 'react-redux'; import { Ecs } from '../../../../../common/ecs'; import { TimelineItem, TimelineNonEcsData } from '../../../../../common/search_strategy'; import { updateTimelineGraphEventId } from '../../../store/timeline/actions'; -import { EventType } from '../../../store/timeline/model'; +import { + TimelineEventsType, + TimelineTypeLiteral, + TimelineType, +} from '../../../../../common/types/timeline'; import { OnPinEvent, OnUnPinEvent } from '../events'; import { ActionIconItem } from './actions/action_icon_item'; import * as i18n from './translations'; -import { TimelineTypeLiteral, TimelineType } from '../../../../../common/types/timeline'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export const omitTypenameAndEmpty = (k: string, v: any): any | undefined => @@ -101,7 +104,7 @@ export const getEventIdToDataMapping = ( }, {}); /** Return eventType raw or signal */ -export const getEventType = (event: Ecs): Omit => { +export const getEventType = (event: Ecs): Omit => { if (!isEmpty(event.signal?.rule?.id)) { return 'signal'; } diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx index 58c87c086df6e..fc0bcb134158c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx @@ -10,7 +10,7 @@ import { inputsModel } from '../../../../common/store'; import { BrowserFields, DocValueFields } from '../../../../common/containers/source'; import { TimelineItem, TimelineNonEcsData } from '../../../../../common/search_strategy'; import { Note } from '../../../../common/lib/note'; -import { ColumnHeaderOptions, EventType } from '../../../../timelines/store/timeline/model'; +import { ColumnHeaderOptions } from '../../../../timelines/store/timeline/model'; import { AddNoteToEvent, UpdateNote } from '../../notes/helpers'; import { OnColumnRemoved, @@ -31,7 +31,7 @@ import { RowRenderer } from './renderers/row_renderer'; import { Sort } from './sort'; import { GraphOverlay } from '../../graph_overlay'; import { DEFAULT_ICON_BUTTON_WIDTH } from '../helpers'; -import { TimelineId, TimelineType } from '../../../../../common/types/timeline'; +import { TimelineEventsType, TimelineId, TimelineType } from '../../../../../common/types/timeline'; export interface BodyProps { addNoteToEvent: AddNoteToEvent; @@ -45,7 +45,7 @@ export interface BodyProps { isEventViewer?: boolean; isSelectAllChecked: boolean; eventIdToNoteIds: Readonly>; - eventType?: EventType; + eventType?: TimelineEventsType; loadingEventIds: Readonly; onColumnRemoved: OnColumnRemoved; onColumnResized: OnColumnResized; @@ -68,7 +68,7 @@ export interface BodyProps { updateNote: UpdateNote; } -export const hasAdditionalActions = (id: string, eventType?: EventType): boolean => +export const hasAdditionalActions = (id: string, eventType?: TimelineEventsType): boolean => id === TimelineId.detectionsPage || id === TimelineId.detectionsRulesDetailsPage || ((id === TimelineId.active && eventType && ['all', 'signal', 'alert'].includes(eventType)) ?? diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/helpers.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/helpers.test.tsx index 61b4c2b23c267..d8a9f7528ddae 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/helpers.test.tsx @@ -112,7 +112,7 @@ describe('helpers', () => { }) ).toEqual({ updatedDestinationGroup: [sourceGroup[moveProviderFromSourceIndex]], - updatedSourceGroup: sourceGroup.filter((_, i) => i !== moveProviderFromSourceIndex), + updatedSourcererScope: sourceGroup.filter((_, i) => i !== moveProviderFromSourceIndex), }); }) ); @@ -138,7 +138,7 @@ describe('helpers', () => { : [...acc, sourceGroup[moveProviderFromSourceIndex], p], [] ), - updatedSourceGroup: sourceGroup.filter((_, i) => i !== moveProviderFromSourceIndex), + updatedSourcererScope: sourceGroup.filter((_, i) => i !== moveProviderFromSourceIndex), }); }) ) @@ -171,7 +171,7 @@ describe('helpers', () => { p.id !== sourceGroup[moveProviderFromSourceIndex].id || i === moveProviderToDestinationIndex ), - updatedSourceGroup: sourceGroup.filter((_, i) => i !== moveProviderFromSourceIndex), + updatedSourcererScope: sourceGroup.filter((_, i) => i !== moveProviderFromSourceIndex), }); }) ) diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/helpers.tsx index 923ef86c0bbc0..00c7f0705f3ce 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/helpers.tsx @@ -42,7 +42,7 @@ export const move = ({ sourceGroup: DataProvidersAnd[]; }): { updatedDestinationGroup: DataProvidersAnd[]; - updatedSourceGroup: DataProvidersAnd[]; + updatedSourcererScope: DataProvidersAnd[]; } => { const sourceClone = [...sourceGroup]; const destinationClone = [...destinationGroup]; @@ -56,7 +56,7 @@ export const move = ({ return { updatedDestinationGroup: deDuplicatedDestinationGroup, - updatedSourceGroup: sourceClone, + updatedSourcererScope: sourceClone, }; }; @@ -169,7 +169,7 @@ export const moveProvidersBetweenGroups = ({ const moveProviderFromSourceIndex = source.index; const moveProviderToDestinationIndex = destination.index; - const { updatedDestinationGroup, updatedSourceGroup } = move({ + const { updatedDestinationGroup, updatedSourcererScope } = move({ destinationGroup, moveProviderFromSourceIndex, moveProviderToDestinationIndex, @@ -180,7 +180,7 @@ export const moveProvidersBetweenGroups = ({ (acc, group, i) => [ ...acc, i === sourceGroupIndex - ? [...updatedSourceGroup] + ? [...updatedSourcererScope] : i === destinationGroupIndex ? [...updatedDestinationGroup] : [...group], diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx index f1bfdd5d33606..d2737de7e75dc 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.test.tsx @@ -6,49 +6,32 @@ import { mount } from 'enzyme'; import React from 'react'; -import { MockedProvider } from 'react-apollo/test-utils'; -import { act } from 'react-dom/test-utils'; import useResizeObserver from 'use-resize-observer/polyfilled'; import '../../../common/mock/match_media'; +import { mockBrowserFields, mockDocValueFields } from '../../../common/containers/source/mock'; + import { - useSignalIndex, - ReturnSignalIndex, -} from '../../../detections/containers/detection_engine/alerts/use_signal_index'; -import { mocksSource } from '../../../common/containers/source/mock'; -// we don't have the types for waitFor just yet, so using "as waitFor" until when we do -import { wait as waitFor } from '@testing-library/react'; -import { defaultHeaders, mockTimelineData, TestProviders } from '../../../common/mock'; -import { Direction } from '../../../graphql/types'; -import { timelineActions } from '../../store/timeline'; + mockIndexNames, + mockIndexPattern, + mockTimelineData, + TestProviders, +} from '../../../common/mock'; -import { Sort } from './body/sort'; -import { mockDataProviders } from './data_providers/mock/mock_data_providers'; -import { StatefulTimeline, Props as StatefulTimelineProps } from './index'; -import { Timeline } from './timeline'; -import { TimelineType, TimelineStatus } from '../../../../common/types/timeline'; +import { StatefulTimeline, OwnProps as StatefulTimelineOwnProps } from './index'; import { useTimelineEvents } from '../../containers/index'; jest.mock('../../containers/index', () => ({ useTimelineEvents: jest.fn(), })); -jest.mock('../../../common/lib/kibana', () => { - const originalModule = jest.requireActual('../../../common/lib/kibana'); - return { - ...originalModule, - useGetUserSavedObjectPermissions: jest.fn(), - }; -}); - +jest.mock('../../../common/lib/kibana'); jest.mock('../../../common/components/url_state/normalize_time_range.ts'); const mockUseResizeObserver: jest.Mock = useResizeObserver as jest.Mock; jest.mock('use-resize-observer/polyfilled'); mockUseResizeObserver.mockImplementation(() => ({})); -const mockUseSignalIndex: jest.Mock = useSignalIndex as jest.Mock; -jest.mock('../../../detections/containers/detection_engine/alerts/use_signal_index'); jest.mock('react-router-dom', () => { const original = jest.requireActual('react-router-dom'); @@ -58,105 +41,37 @@ jest.mock('react-router-dom', () => { }; }); jest.mock('../flyout/header_with_close_button'); +jest.mock('../../../common/containers/sourcerer', () => { + const originalModule = jest.requireActual('../../../common/containers/sourcerer'); + + return { + ...originalModule, + useSourcererScope: jest.fn().mockReturnValue({ + browserFields: mockBrowserFields, + docValueFields: mockDocValueFields, + loading: false, + indexPattern: mockIndexPattern, + selectedPatterns: mockIndexNames, + }), + }; +}); describe('StatefulTimeline', () => { - let props = {} as StatefulTimelineProps; - const sort: Sort = { - columnId: '@timestamp', - sortDirection: Direction.desc, + const props: StatefulTimelineOwnProps = { + id: 'id', + onClose: jest.fn(), + usersViewing: [], }; - const startDate = '2018-03-23T18:49:23.132Z'; - const endDate = '2018-03-24T03:33:52.253Z'; - - const mocks = mocksSource; beforeEach(() => { (useTimelineEvents as jest.Mock).mockReturnValue([false, { events: mockTimelineData }]); - props = { - addProvider: timelineActions.addProvider, - columns: defaultHeaders, - createTimeline: timelineActions.createTimeline, - dataProviders: mockDataProviders, - eventType: 'raw', - end: endDate, - filters: [], - graphEventId: undefined, - id: 'foo', - isLive: false, - isSaving: false, - isTimelineExists: false, - itemsPerPage: 5, - itemsPerPageOptions: [5, 10, 20], - kqlMode: 'search', - kqlQueryExpression: '', - onClose: jest.fn(), - onDataProviderEdited: timelineActions.dataProviderEdited, - removeColumn: timelineActions.removeColumn, - removeProvider: timelineActions.removeProvider, - show: true, - showCallOutUnauthorizedMsg: false, - sort, - start: startDate, - status: TimelineStatus.active, - timelineType: TimelineType.default, - updateColumns: timelineActions.updateColumns, - updateDataProviderEnabled: timelineActions.updateDataProviderEnabled, - updateDataProviderExcluded: timelineActions.updateDataProviderExcluded, - updateDataProviderKqlQuery: timelineActions.updateDataProviderKqlQuery, - updateDataProviderType: timelineActions.updateDataProviderType, - updateHighlightedDropAndProviderId: timelineActions.updateHighlightedDropAndProviderId, - updateItemsPerPage: timelineActions.updateItemsPerPage, - updateItemsPerPageOptions: timelineActions.updateItemsPerPageOptions, - updateSort: timelineActions.updateSort, - upsertColumn: timelineActions.upsertColumn, - usersViewing: ['elastic'], - }; }); - describe('indexToAdd', () => { - test('Make sure that indexToAdd return an unknown index if signalIndex does not exist', async () => { - mockUseSignalIndex.mockImplementation(() => ({ - loading: false, - signalIndexExists: false, - signalIndexName: undefined, - })); - const wrapper = mount( - - - - - - ); - await act(async () => { - await waitFor(() => { - wrapper.update(); - const timeline = wrapper.find(Timeline); - expect(timeline.props().indexToAdd).toEqual([ - 'no-alert-index-049FC71A-4C2C-446F-9901-37XMC5024C51', - ]); - }); - }); - }); - - test('Make sure that indexToAdd return siem signal index if signalIndex exist', async () => { - mockUseSignalIndex.mockImplementation(() => ({ - loading: false, - signalIndexExists: true, - signalIndexName: 'mock-siem-signals-index', - })); - const wrapper = mount( - - - - - - ); - await act(async () => { - await waitFor(() => { - wrapper.update(); - const timeline = wrapper.find(Timeline); - expect(timeline.props().indexToAdd).toEqual(['mock-siem-signals-index']); - }); - }); - }); + test('renders ', () => { + const wrapper = mount( + + + + ); + expect(wrapper.find('[data-test-subj="timeline"]')).toBeTruthy(); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx index c170c93ee6083..ccdb0793bc03f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx @@ -5,13 +5,10 @@ */ import { isEmpty } from 'lodash/fp'; -import React, { useEffect, useCallback, useMemo } from 'react'; +import React, { useEffect, useCallback } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import deepEqual from 'fast-deep-equal'; -import { NO_ALERT_INDEX } from '../../../../common/constants'; -import { useWithSource } from '../../../common/containers/source'; -import { useSignalIndex } from '../../../detections/containers/detection_engine/alerts/use_signal_index'; import { inputsModel, inputsSelectors, State } from '../../../common/store'; import { timelineActions, timelineSelectors } from '../../store/timeline'; import { ColumnHeaderOptions, TimelineModel } from '../../../timelines/store/timeline/model'; @@ -26,6 +23,8 @@ import { OnToggleDataProviderType, } from './events'; import { Timeline } from './timeline'; +import { useSourcererScope } from '../../../common/containers/sourcerer'; +import { SourcererScopeName } from '../../../common/store/sourcerer/model'; export interface OwnProps { id: string; @@ -40,7 +39,6 @@ const StatefulTimelineComponent = React.memo( columns, createTimeline, dataProviders, - eventType, end, filters, graphEventId, @@ -69,19 +67,13 @@ const StatefulTimelineComponent = React.memo( upsertColumn, usersViewing, }) => { - const { loading, signalIndexExists, signalIndexName } = useSignalIndex(); - - const indexToAdd = useMemo(() => { - if ( - eventType && - signalIndexExists && - signalIndexName != null && - ['signal', 'alert', 'all'].includes(eventType) - ) { - return [signalIndexName]; - } - return [NO_ALERT_INDEX]; // Following index does not exist so we won't show any events; - }, [eventType, signalIndexExists, signalIndexName]); + const { + browserFields, + docValueFields, + loading, + indexPattern, + selectedPatterns, + } = useSourcererScope(SourcererScopeName.timeline); const onDataProviderRemoved: OnDataProviderRemoved = useCallback( (providerId: string, andProviderId?: string) => @@ -160,22 +152,16 @@ const StatefulTimelineComponent = React.memo( }); } }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [columns, id] + [columns, id, removeColumn, upsertColumn] ); useEffect(() => { if (createTimeline != null && !isTimelineExists) { - createTimeline({ id, columns: defaultHeaders, show: false }); + createTimeline({ id, columns: defaultHeaders, indexNames: selectedPatterns, show: false }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const { docValueFields, indexPattern, browserFields, loading: isLoadingSource } = useWithSource( - 'default', - indexToAdd - ); - return ( ( dataProviders={dataProviders!} docValueFields={docValueFields} end={end} - eventType={eventType} filters={filters} graphEventId={graphEventId} id={id} indexPattern={indexPattern} - indexToAdd={indexToAdd} + indexNames={selectedPatterns} isLive={isLive} - isLoadingSource={isLoadingSource} isSaving={isSaving} itemsPerPage={itemsPerPage!} itemsPerPageOptions={itemsPerPageOptions!} kqlMode={kqlMode} kqlQueryExpression={kqlQueryExpression} - loadingIndexName={loading} + loadingSourcerer={loading} onChangeItemsPerPage={onChangeItemsPerPage} onClose={onClose} onDataProviderEdited={onDataProviderEditedLocal} @@ -215,10 +199,8 @@ const StatefulTimelineComponent = React.memo( /> ); }, - // eslint-disable-next-line complexity (prevProps, nextProps) => { return ( - prevProps.eventType === nextProps.eventType && prevProps.end === nextProps.end && prevProps.graphEventId === nextProps.graphEventId && prevProps.id === nextProps.id && diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.test.tsx index 5b3bc72fc37ca..7da3cf940da50 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.test.tsx @@ -93,15 +93,18 @@ describe('useCreateTimelineButton', () => { wrapper.find('[data-test-subj="timeline-new"]').first().simulate('click'); expect(mockDispatch.mock.calls[0][0].type).toEqual( - 'x-pack/security_solution/local/timeline/CREATE_TIMELINE' + 'x-pack/security_solution/local/sourcerer/SET_SELECTED_INDEX_PATTERNS' ); expect(mockDispatch.mock.calls[1][0].type).toEqual( - 'x-pack/security_solution/local/inputs/ADD_GLOBAL_LINK_TO' + 'x-pack/security_solution/local/timeline/CREATE_TIMELINE' ); expect(mockDispatch.mock.calls[2][0].type).toEqual( - 'x-pack/security_solution/local/inputs/ADD_TIMELINE_LINK_TO' + 'x-pack/security_solution/local/inputs/ADD_GLOBAL_LINK_TO' ); expect(mockDispatch.mock.calls[3][0].type).toEqual( + 'x-pack/security_solution/local/inputs/ADD_TIMELINE_LINK_TO' + ); + expect(mockDispatch.mock.calls[4][0].type).toEqual( 'x-pack/security_solution/local/inputs/SET_RELATIVE_RANGE_DATE_PICKER' ); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.tsx index 97f3b1df011ff..3919ee21b2a90 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.tsx @@ -3,8 +3,8 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import React, { useCallback, useMemo } from 'react'; +import { shallowEqual, useDispatch, useSelector } from 'react-redux'; import { EuiButton, EuiButtonEmpty } from '@elastic/eui'; import { defaultHeaders } from '../body/column_headers/default_headers'; import { timelineActions } from '../../../store/timeline'; @@ -15,6 +15,9 @@ import { TimelineTypeLiteral, } from '../../../../../common/types/timeline'; import { inputsActions, inputsSelectors } from '../../../../common/store/inputs'; +import { sourcererActions, sourcererSelectors } from '../../../../common/store/sourcerer'; +import { State } from '../../../../common/store'; +import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; export const useCreateTimelineButton = ({ timelineId, @@ -26,6 +29,11 @@ export const useCreateTimelineButton = ({ closeGearMenu?: () => void; }) => { const dispatch = useDispatch(); + const existingIndexNamesSelector = useMemo( + () => sourcererSelectors.getAllExistingIndexNamesSelector(), + [] + ); + const existingIndexNames = useSelector(existingIndexNamesSelector, shallowEqual); const { timelineFullScreen, setTimelineFullScreen } = useFullScreen(); const globalTimeRange = useSelector(inputsSelectors.globalTimeRangeSelector); const createTimeline = useCallback( @@ -33,12 +41,19 @@ export const useCreateTimelineButton = ({ if (id === TimelineId.active && timelineFullScreen) { setTimelineFullScreen(false); } + dispatch( + sourcererActions.setSelectedIndexPatterns({ + id: SourcererScopeName.timeline, + selectedPatterns: existingIndexNames, + }) + ); dispatch( timelineActions.createTimeline({ id, columns: defaultHeaders, show, timelineType, + indexNames: existingIndexNames, }) ); dispatch(inputsActions.addGlobalLinkTo({ linkToId: 'timeline' })); @@ -59,7 +74,14 @@ export const useCreateTimelineButton = ({ ); } }, - [dispatch, globalTimeRange, setTimelineFullScreen, timelineFullScreen, timelineType] + [ + existingIndexNames, + dispatch, + globalTimeRange, + setTimelineFullScreen, + timelineFullScreen, + timelineType, + ] ); const handleButtonClick = useCallback(() => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/index.tsx index 7ee7e12c0ef62..166705128ce02 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/index.tsx @@ -24,11 +24,14 @@ import { inputsModel, inputsSelectors, } from '../../../../common/store'; +import { TimelineEventsType } from '../../../../../common/types/timeline'; import { timelineActions, timelineSelectors } from '../../../store/timeline'; -import { KqlMode, TimelineModel, EventType } from '../../../../timelines/store/timeline/model'; +import { KqlMode, TimelineModel } from '../../../../timelines/store/timeline/model'; import { timelineDefaults } from '../../../../timelines/store/timeline/defaults'; import { dispatchUpdateReduxTime } from '../../../../common/components/super_date_picker'; import { SearchOrFilter } from './search_or_filter'; +import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; +import { sourcererActions } from '../../../../common/store/sourcerer'; interface OwnProps { browserFields: BrowserFields; @@ -62,7 +65,7 @@ const StatefulSearchOrFilterComponent = React.memo( timelineId, to, toStr, - updateEventType, + updateEventTypeAndIndexesName, updateKqlMode, updateReduxTime, }) => { @@ -111,13 +114,14 @@ const StatefulSearchOrFilterComponent = React.memo( [timelineId, setSavedQueryId] ); - const handleUpdateEventType = useCallback( - (newEventType: EventType) => - updateEventType({ + const handleUpdateEventTypeAndIndexesName = useCallback( + (newEventType: TimelineEventsType, indexNames: string[]) => + updateEventTypeAndIndexesName({ id: timelineId, eventType: newEventType, + indexNames, }), - [timelineId, updateEventType] + [timelineId, updateEventTypeAndIndexesName] ); return ( @@ -143,7 +147,7 @@ const StatefulSearchOrFilterComponent = React.memo( timelineId={timelineId} to={to} toStr={toStr} - updateEventType={handleUpdateEventType} + updateEventTypeAndIndexesName={handleUpdateEventTypeAndIndexesName} updateKqlMode={updateKqlMode!} updateReduxTime={updateReduxTime} /> @@ -211,8 +215,24 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ filterQuery, }) ), - updateEventType: ({ id, eventType }: { id: string; eventType: EventType }) => - dispatch(timelineActions.updateEventType({ id, eventType })), + updateEventTypeAndIndexesName: ({ + id, + eventType, + indexNames, + }: { + id: string; + eventType: TimelineEventsType; + indexNames: string[]; + }) => { + dispatch(timelineActions.updateEventType({ id, eventType })); + dispatch(timelineActions.updateIndexNames({ id, indexNames })); + dispatch( + sourcererActions.setSelectedIndexPatterns({ + id: SourcererScopeName.timeline, + selectedPatterns: indexNames, + }) + ); + }, updateKqlMode: ({ id, kqlMode }: { id: string; kqlMode: KqlMode }) => dispatch(timelineActions.updateKqlMode({ id, kqlMode })), setKqlFilterQueryDraft: ({ diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.tsx index fc2bd1c21abdc..16200f4e5ef9a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.tsx @@ -4,17 +4,47 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiHealth, EuiSuperSelect } from '@elastic/eui'; -import React, { memo } from 'react'; +import { + EuiAccordion, + EuiButton, + EuiButtonEmpty, + EuiRadioGroup, + EuiComboBox, + EuiComboBoxOptionOption, + EuiHealth, + EuiIcon, + EuiFlexGroup, + EuiFlexItem, + EuiPopover, + EuiPopoverTitle, + EuiSpacer, + EuiText, + EuiToolTip, +} from '@elastic/eui'; +import deepEqual from 'fast-deep-equal'; +import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; +import { useSelector } from 'react-redux'; import styled from 'styled-components'; -import { EventType } from '../../../../timelines/store/timeline/model'; +import { State } from '../../../../common/store'; +import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; +import { TimelineEventsType } from '../../../../../common/types/timeline'; +import { getSourcererScopeSelector, SourcererScopeSelector } from './selectors'; import * as i18n from './translations'; -interface EventTypeOptionItem { - value: EventType; - inputDisplay: React.ReactElement; -} +const PopoverContent = styled.div` + width: 600px; +`; + +const ResetButton = styled(EuiButtonEmpty)` + width: fit-content; +`; + +const MyEuiButton = styled(EuiButton)` + .euiHealth { + vertical-align: middle; + } +`; const AllEuiHealth = styled(EuiHealth)` margin-left: -2px; @@ -36,6 +66,18 @@ const WarningEuiHealth = styled(EuiHealth)` } `; +const AdvancedSettings = styled(EuiText)` + color: ${({ theme }) => theme.eui.euiColorPrimary}; +`; + +const ConfigHelper = styled(EuiText)` + margin-left: 4px; +`; + +const Filter = styled(EuiRadioGroup)` + margin-left: 4px; +`; + const PickEventContainer = styled.div` .euiSuperSelect { width: 170px; @@ -46,43 +88,309 @@ const PickEventContainer = styled.div` } `; -export const eventTypeOptions: EventTypeOptionItem[] = [ +const getEventTypeOptions = (isCustomDisabled: boolean = true) => [ { - value: 'all', - inputDisplay: ( + id: 'all', + label: ( {i18n.ALL_EVENT} ), }, { - value: 'raw', - inputDisplay: {i18n.RAW_EVENT}, + id: 'raw', + label: {i18n.RAW_EVENT}, }, { - value: 'alert', - inputDisplay: {i18n.DETECTION_ALERTS_EVENT}, + id: 'alert', + label: {i18n.DETECTION_ALERTS_EVENT}, + }, + { + id: 'custom', + label: <>{i18n.CUSTOM_INDEX_PATTERNS}, + disabled: isCustomDisabled, }, ]; interface PickEventTypeProps { - eventType: EventType; - onChangeEventType: (value: EventType) => void; + eventType: TimelineEventsType; + onChangeEventTypeAndIndexesName: (value: TimelineEventsType, indexNames: string[]) => void; } const PickEventTypeComponents: React.FC = ({ - eventType, - onChangeEventType, + eventType = 'all', + onChangeEventTypeAndIndexesName, }) => { + const [isPopoverOpen, setPopover] = useState(false); + const [showAdvanceSettings, setAdvanceSettings] = useState(eventType === 'custom'); + const [filterEventType, setFilterEventType] = useState(eventType); + const sourcererScopeSelector = useMemo(getSourcererScopeSelector, []); + const { configIndexPatterns, kibanaIndexPatterns, signalIndexName, sourcererScope } = useSelector< + State, + SourcererScopeSelector + >((state) => sourcererScopeSelector(state, SourcererScopeName.timeline), deepEqual); + const [selectedOptions, setSelectedOptions] = useState>>( + sourcererScope.selectedPatterns.map((indexSelected) => ({ + label: indexSelected, + value: indexSelected, + })) + ); + + const indexesPatternOptions = useMemo( + () => + [ + ...configIndexPatterns, + ...kibanaIndexPatterns.map((kip) => kip.title), + signalIndexName, + ].reduce>>((acc, index) => { + if (index != null && !acc.some((o) => o.label.includes(index))) { + return [...acc, { label: index, value: index }]; + } + return acc; + }, []), + [configIndexPatterns, kibanaIndexPatterns, signalIndexName] + ); + + const renderOption = useCallback( + (option) => { + const { value } = option; + if (kibanaIndexPatterns.some((kip) => kip.title === value)) { + return ( + <> + {value} + + ); + } + return <>{value}; + }, + [kibanaIndexPatterns] + ); + + const onChangeCombo = useCallback( + (newSelectedOptions: Array>) => { + const localSelectedPatterns = newSelectedOptions.map((nso) => nso.label); + if ( + localSelectedPatterns.sort().join() === + [...configIndexPatterns, signalIndexName].sort().join() + ) { + setFilterEventType('all'); + } else if (localSelectedPatterns.sort().join() === configIndexPatterns.sort().join()) { + setFilterEventType('raw'); + } else if (localSelectedPatterns.sort().join() === signalIndexName) { + setFilterEventType('alert'); + } else { + setFilterEventType('custom'); + } + + setSelectedOptions(newSelectedOptions); + }, + [configIndexPatterns, signalIndexName] + ); + + const onChangeFilter = useCallback( + (filter) => { + setFilterEventType(filter); + if (filter === 'all') { + setSelectedOptions( + [...configIndexPatterns, signalIndexName ?? ''].map((indexSelected) => ({ + label: indexSelected, + value: indexSelected, + })) + ); + } else if (filter === 'raw') { + setSelectedOptions( + configIndexPatterns.map((indexSelected) => ({ + label: indexSelected, + value: indexSelected, + })) + ); + } else if (filter === 'alert') { + setSelectedOptions([ + { + label: signalIndexName ?? '', + value: signalIndexName ?? '', + }, + ]); + } else if (filter === 'kibana') { + setSelectedOptions( + kibanaIndexPatterns.map((kip) => ({ + label: kip.title, + value: kip.title, + })) + ); + } + }, + [configIndexPatterns, kibanaIndexPatterns, signalIndexName] + ); + + const togglePopover = useCallback( + () => setPopover((prevIsPopoverOpen) => !prevIsPopoverOpen), + [] + ); + + const closePopover = useCallback(() => setPopover(false), []); + + const handleSaveIndices = useCallback(() => { + onChangeEventTypeAndIndexesName( + filterEventType, + selectedOptions.map((so) => so.label) + ); + setPopover(false); + }, [filterEventType, onChangeEventTypeAndIndexesName, selectedOptions]); + + const resetDataSources = useCallback(() => { + setSelectedOptions( + sourcererScope.selectedPatterns.map((indexSelected) => ({ + label: indexSelected, + value: indexSelected, + })) + ); + setFilterEventType(eventType); + }, [eventType, sourcererScope.selectedPatterns]); + + const comboBox = useMemo( + () => ( + + ), + [onChangeCombo, indexesPatternOptions, renderOption, selectedOptions] + ); + + const filterOptions = useMemo(() => getEventTypeOptions(filterEventType !== 'custom'), [ + filterEventType, + ]); + + const filter = useMemo( + () => ( + + ), + [filterEventType, filterOptions, onChangeFilter] + ); + + const button = useMemo(() => { + const options = getEventTypeOptions(); + return ( + + {options.find((opt) => opt.id === eventType)?.label} + + ); + }, [eventType, sourcererScope.loading, togglePopover]); + + const tooltipContent = useMemo( + () => (isPopoverOpen ? null : sourcererScope.selectedPatterns.sort().join(', ')), + [isPopoverOpen, sourcererScope.selectedPatterns] + ); + + const ButtonContent = useMemo( + () => ( + + {showAdvanceSettings + ? i18n.HIDE_INDEX_PATTERNS_ADVANCED_SETTINGS + : i18n.SHOW_INDEX_PATTERNS_ADVANCED_SETTINGS} + + ), + [showAdvanceSettings] + ); + + useEffect(() => { + const newSelectedOptions = sourcererScope.selectedPatterns.map((indexSelected) => ({ + label: indexSelected, + value: indexSelected, + })); + setSelectedOptions((prevSelectedOptions) => { + if (!deepEqual(newSelectedOptions, prevSelectedOptions)) { + return newSelectedOptions; + } + return prevSelectedOptions; + }); + }, [sourcererScope.selectedPatterns]); + + useEffect(() => { + setFilterEventType((prevFilter) => (prevFilter !== eventType ? eventType : prevFilter)); + setAdvanceSettings(eventType === 'custom'); + }, [eventType]); + return ( - + + + + + <>{i18n.SELECT_INDEX_PATTERNS} + + + {filter} + + + <> + + {comboBox} + + + {!showAdvanceSettings && ( + <> + + + {i18n.CONFIGURE_INDEX_PATTERNS} + + + )} + + + + + {i18n.DATA_SOURCES_RESET} + + + + + {i18n.SAVE_INDEX_PATTERNS} + + + + + + ); }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/search_or_filter.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/search_or_filter.tsx index e04cef4ad8d93..32a516497f607 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/search_or_filter.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/search_or_filter.tsx @@ -15,7 +15,8 @@ import { } from '../../../../../../../../src/plugins/data/public'; import { BrowserFields } from '../../../../common/containers/source'; import { KueryFilterQuery, KueryFilterQueryKind } from '../../../../common/store'; -import { KqlMode, EventType } from '../../../../timelines/store/timeline/model'; +import { TimelineEventsType } from '../../../../../common/types/timeline'; +import { KqlMode } from '../../../../timelines/store/timeline/model'; import { DispatchUpdateReduxTime } from '../../../../common/components/super_date_picker'; import { DataProvider } from '../data_providers/data_provider'; import { QueryBarTimeline } from '../query_bar'; @@ -47,7 +48,7 @@ interface Props { applyKqlFilterQuery: (expression: string, kind: KueryFilterQueryKind) => void; browserFields: BrowserFields; dataProviders: DataProvider[]; - eventType: EventType; + eventType: TimelineEventsType; filterManager: FilterManager; filterQuery: KueryFilterQuery; filterQueryDraft: KueryFilterQuery; @@ -66,7 +67,7 @@ interface Props { savedQueryId: string | null; to: string; toStr: string; - updateEventType: (eventType: EventType) => void; + updateEventTypeAndIndexesName: (eventType: TimelineEventsType, indexNames: string[]) => void; updateReduxTime: DispatchUpdateReduxTime; } @@ -114,7 +115,7 @@ export const SearchOrFilter = React.memo( setSavedQueryId, to, toStr, - updateEventType, + updateEventTypeAndIndexesName, updateKqlMode, updateReduxTime, }) => { @@ -167,7 +168,10 @@ export const SearchOrFilter = React.memo( /> - + diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/selectors.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/selectors.tsx new file mode 100644 index 0000000000000..2fdcf7a0eb0c1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/selectors.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { State } from '../../../../common/store'; +import { sourcererSelectors } from '../../../../common/store/selectors'; +import { + KibanaIndexPatterns, + ManageScope, + SourcererScopeName, +} from '../../../../common/store/sourcerer/model'; + +export interface SourcererScopeSelector { + configIndexPatterns: string[]; + kibanaIndexPatterns: KibanaIndexPatterns; + signalIndexName: string | null; + sourcererScope: ManageScope; +} + +export const getSourcererScopeSelector = () => { + const getkibanaIndexPatternsSelector = sourcererSelectors.kibanaIndexPatternsSelector(); + const getScopesSelector = sourcererSelectors.scopesSelector(); + const getConfigIndexPatternsSelector = sourcererSelectors.configIndexPatternsSelector(); + const getSignalIndexNameSelector = sourcererSelectors.signalIndexNameSelector(); + + const mapStateToProps = (state: State, scopeId: SourcererScopeName): SourcererScopeSelector => { + const kibanaIndexPatterns = getkibanaIndexPatternsSelector(state); + const scope = getScopesSelector(state)[scopeId]; + const configIndexPatterns = getConfigIndexPatternsSelector(state); + const signalIndexName = getSignalIndexNameSelector(state); + + return { + kibanaIndexPatterns, + configIndexPatterns, + signalIndexName, + sourcererScope: scope, + }; + }; + + return mapStateToProps; +}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/translations.ts index b5c78c458697c..f595881a57d03 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/translations.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/translations.ts @@ -73,14 +73,14 @@ export const FILTER_OR_SEARCH_WITH_KQL = i18n.translate( export const ALL_EVENT = i18n.translate( 'xpack.securitySolution.timeline.searchOrFilter.eventTypeAllEvent', { - defaultMessage: 'All', + defaultMessage: 'All data sources', } ); export const RAW_EVENT = i18n.translate( 'xpack.securitySolution.timeline.searchOrFilter.eventTypeRawEvent', { - defaultMessage: 'Raw events', + defaultMessage: 'Events', } ); @@ -90,3 +90,59 @@ export const DETECTION_ALERTS_EVENT = i18n.translate( defaultMessage: 'Detection Alerts', } ); + +export const CUSTOM_INDEX_PATTERNS = i18n.translate( + 'xpack.securitySolution.timeline.searchOrFilter.customeIndexNames', + { + defaultMessage: 'Custom', + } +); + +export const SELECT_INDEX_PATTERNS = i18n.translate( + 'xpack.securitySolution.timeline.searchOrFilter.indexPatterns.help', + { + defaultMessage: 'Data sources selection', + } +); + +export const CONFIGURE_INDEX_PATTERNS = i18n.translate( + 'xpack.securitySolution.timeline.searchOrFilter.indexPatterns.configure', + { + defaultMessage: 'View data sources associated with each of the above selections', + } +); + +export const SAVE_INDEX_PATTERNS = i18n.translate( + 'xpack.securitySolution.timeline.searchOrFilter.indexPatterns.save', + { + defaultMessage: 'Save', + } +); + +export const SHOW_INDEX_PATTERNS_ADVANCED_SETTINGS = i18n.translate( + 'xpack.securitySolution.timeline.searchOrFilter.indexPatterns.showAdvancedSettings', + { + defaultMessage: 'Show Advanced', + } +); + +export const HIDE_INDEX_PATTERNS_ADVANCED_SETTINGS = i18n.translate( + 'xpack.securitySolution.timeline.searchOrFilter.indexPatterns.hideAdvancedSettings', + { + defaultMessage: 'Hide Advanced', + } +); + +export const DATA_SOURCES_RESET = i18n.translate( + 'xpack.securitySolution.timeline.searchOrFilter.indexPatterns.resetSettings', + { + defaultMessage: 'Reset', + } +); + +export const PICK_INDEX_PATTERNS = i18n.translate( + 'xpack.securitySolution.timeline.searchOrFilter.indexPatterns.pickIndexPatternsCombo', + { + defaultMessage: 'Pick index patterns', + } +); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/styles.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/styles.tsx index 5c992fd640a97..e898779eacce9 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/styles.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/styles.tsx @@ -8,7 +8,7 @@ import { EuiLoadingSpinner } from '@elastic/eui'; import { rgba } from 'polished'; import styled, { createGlobalStyle } from 'styled-components'; -import { EventType } from '../../../timelines/store/timeline/model'; +import { TimelineEventsType } from '../../../../common/types/timeline'; import { IS_TIMELINE_FIELD_DRAGGING_CLASS_NAME } from '../../../common/components/drag_and_drop/helpers'; /** @@ -113,10 +113,10 @@ export const EventsThGroupData = styled.div.attrs(({ className = '' }) => ({ } `; -export const EventsTh = styled.div.attrs(({ className = '' }) => ({ +export const EventsTh = styled.div.attrs<{ role: string }>(({ className = '' }) => ({ className: `siemEventsTable__th ${className}`, role: 'columnheader', -}))` +}))<{ role?: string }>` align-items: center; display: flex; flex-shrink: 0; @@ -173,7 +173,7 @@ export const EventsTbody = styled.div.attrs(({ className = '' }) => ({ export const EventsTrGroup = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__trGroup ${className}`, -}))<{ className?: string; eventType: Omit; showLeftBorder: boolean }>` +}))<{ className?: string; eventType: Omit; showLeftBorder: boolean }>` border-bottom: ${({ theme }) => theme.eui.euiBorderWidthThin} solid ${({ theme }) => theme.eui.euiColorLightShade}; ${({ theme, eventType, showLeftBorder }) => diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.test.tsx index 887a6a546d2b7..bde1e7bf5829a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.test.tsx @@ -10,7 +10,12 @@ import useResizeObserver from 'use-resize-observer/polyfilled'; import { mockBrowserFields } from '../../../common/containers/source/mock'; import { Direction } from '../../../graphql/types'; -import { defaultHeaders, mockTimelineData, mockIndexPattern } from '../../../common/mock'; +import { + defaultHeaders, + mockTimelineData, + mockIndexPattern, + mockIndexNames, +} from '../../../common/mock'; import '../../../common/mock/match_media'; import { TestProviders } from '../../../common/mock/test_providers'; @@ -23,7 +28,7 @@ import { TimelineComponent, Props as TimelineComponentProps } from './timeline'; import { Sort } from './body/sort'; import { mockDataProviders } from './data_providers/mock/mock_data_providers'; import { useMountAppended } from '../../../common/utils/use_mount_appended'; -import { TimelineStatus, TimelineType } from '../../../../common/types/timeline'; +import { TimelineId, TimelineStatus, TimelineType } from '../../../../common/types/timeline'; import { useTimelineEvents } from '../../containers/index'; import { useTimelineEventsDetails } from '../../containers/details/index'; @@ -94,22 +99,20 @@ describe('Timeline', () => { props = { browserFields: mockBrowserFields, columns: defaultHeaders, - id: 'foo', dataProviders: mockDataProviders, docValueFields: [], end: endDate, - eventType: 'raw' as TimelineComponentProps['eventType'], filters: [], + id: TimelineId.test, + indexNames: mockIndexNames, indexPattern, - indexToAdd: [], isLive: false, - isLoadingSource: false, isSaving: false, itemsPerPage: 5, itemsPerPageOptions: [5, 10, 20], kqlMode: 'search' as TimelineComponentProps['kqlMode'], kqlQueryExpression: '', - loadingIndexName: false, + loadingSourcerer: false, onChangeItemsPerPage: jest.fn(), onClose: jest.fn(), onDataProviderEdited: jest.fn(), @@ -119,12 +122,12 @@ describe('Timeline', () => { onToggleDataProviderType: jest.fn(), show: true, showCallOutUnauthorizedMsg: false, - start: startDate, sort, + start: startDate, status: TimelineStatus.active, + timelineType: TimelineType.default, toggleColumn: jest.fn(), usersViewing: ['elastic'], - timelineType: TimelineType.default, }; }); @@ -174,7 +177,7 @@ describe('Timeline', () => { test('it does NOT render the timeline table when the source is loading', () => { const wrapper = mount( - + ); @@ -211,16 +214,6 @@ describe('Timeline', () => { expect(wrapper.find('[data-test-subj="table-pagination"]').exists()).toEqual(false); }); - test('it defaults to showing `All`', () => { - const wrapper = mount( - - - - ); - - expect(wrapper.find('[data-test-subj="pick-event-type"] button').text()).toEqual('All'); - }); - it('it shows the timeline footer', () => { const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.tsx index 434c7075a9470..d1a25e6f3e1a4 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.tsx @@ -14,7 +14,7 @@ import { BrowserFields, DocValueFields } from '../../../common/containers/source import { Direction } from '../../../../common/search_strategy'; import { useTimelineEvents } from '../../containers/index'; import { useKibana } from '../../../common/lib/kibana'; -import { ColumnHeaderOptions, KqlMode, EventType } from '../../../timelines/store/timeline/model'; +import { ColumnHeaderOptions, KqlMode } from '../../../timelines/store/timeline/model'; import { defaultHeaders } from './body/column_headers/default_headers'; import { Sort } from './body/sort'; import { StatefulBody } from './body/stateful_body'; @@ -99,20 +99,18 @@ export interface Props { dataProviders: DataProvider[]; docValueFields: DocValueFields[]; end: string; - eventType?: EventType; filters: Filter[]; graphEventId?: string; id: string; + indexNames: string[]; indexPattern: IIndexPattern; - indexToAdd: string[]; isLive: boolean; - isLoadingSource: boolean; isSaving: boolean; itemsPerPage: number; itemsPerPageOptions: number[]; kqlMode: KqlMode; kqlQueryExpression: string; - loadingIndexName: boolean; + loadingSourcerer: boolean; onChangeItemsPerPage: OnChangeItemsPerPage; onClose: () => void; onDataProviderEdited: OnDataProviderEdited; @@ -122,12 +120,12 @@ export interface Props { onToggleDataProviderType: OnToggleDataProviderType; show: boolean; showCallOutUnauthorizedMsg: boolean; - start: string; sort: Sort; + start: string; status: TimelineStatusLiteral; + timelineType: TimelineType; toggleColumn: (column: ColumnHeaderOptions) => void; usersViewing: string[]; - timelineType: TimelineType; } /** The parent Timeline component */ @@ -137,20 +135,18 @@ export const TimelineComponent: React.FC = ({ dataProviders, docValueFields, end, - eventType, filters, graphEventId, id, indexPattern, - indexToAdd, + indexNames, isLive, - isLoadingSource, + loadingSourcerer, isSaving, itemsPerPage, itemsPerPageOptions, kqlMode, kqlQueryExpression, - loadingIndexName, onChangeItemsPerPage, onClose, onDataProviderEdited, @@ -204,11 +200,11 @@ export const TimelineComponent: React.FC = ({ const canQueryTimeline = useMemo( () => combinedQueries != null && - isLoadingSource != null && - !isLoadingSource && + loadingSourcerer != null && + !loadingSourcerer && !isEmpty(start) && !isEmpty(end), - [isLoadingSource, combinedQueries, start, end] + [loadingSourcerer, combinedQueries, start, end] ); const columnsHeader = isEmpty(columns) ? defaultHeaders : columns; const timelineQueryFields = useMemo(() => { @@ -223,16 +219,13 @@ export const TimelineComponent: React.FC = ({ [sort.columnId, sort.sortDirection] ); const [isQueryLoading, setIsQueryLoading] = useState(false); - const { initializeTimeline, setIndexToAdd, setIsTimelineLoading } = useManageTimeline(); - + const { initializeTimeline, setIsTimelineLoading } = useManageTimeline(); useEffect(() => { initializeTimeline({ filterManager, id, - indexToAdd, }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [initializeTimeline, filterManager, id]); const [ loading, @@ -240,24 +233,19 @@ export const TimelineComponent: React.FC = ({ ] = useTimelineEvents({ docValueFields, endDate: end, - eventType, id, - indexToAdd, + indexNames, fields: timelineQueryFields, limit: itemsPerPage, filterQuery: combinedQueries?.filterQuery ?? '', startDate: start, - skip: canQueryTimeline, + skip: !canQueryTimeline, sort: timelineQuerySortField, }); useEffect(() => { - setIsTimelineLoading({ id, isLoading: isQueryLoading || loadingIndexName }); - }, [loadingIndexName, id, isQueryLoading, setIsTimelineLoading]); - - useEffect(() => { - setIndexToAdd({ id, indexToAdd }); - }, [id, indexToAdd, setIndexToAdd]); + setIsTimelineLoading({ id, isLoading: isQueryLoading || loadingSourcerer }); + }, [loadingSourcerer, id, isQueryLoading, setIsTimelineLoading]); useEffect(() => { setIsQueryLoading(loading); @@ -329,7 +317,7 @@ export const TimelineComponent: React.FC = ({ height={footerHeight} id={id} isLive={isLive} - isLoading={loading || loadingIndexName} + isLoading={loading || loadingSourcerer} itemsCount={events.length} itemsPerPage={itemsPerPage} itemsPerPageOptions={itemsPerPageOptions} diff --git a/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx index af99c75ae701a..cd72ffb8ac803 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx @@ -9,7 +9,6 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import deepEqual from 'fast-deep-equal'; import { inputsModel } from '../../../common/store'; -import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import { useKibana } from '../../../common/lib/kibana'; import { DocValueFields, @@ -18,6 +17,7 @@ import { TimelineEventsDetailsRequestOptions, TimelineEventsDetailsStrategyResponse, } from '../../../../common/search_strategy'; +import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/plugins/data/public'; export interface EventsArgs { detailsData: TimelineEventsDetailsItem[] | null; } @@ -35,10 +35,9 @@ export const useTimelineEventsDetails = ({ eventId, skip, }: UseTimelineEventsDetailsProps): [boolean, EventsArgs['detailsData']] => { - const { data, notifications, uiSettings } = useKibana().services; + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); const [loading, setLoading] = useState(false); const [ timelineDetailsRequest, @@ -66,13 +65,13 @@ export const useTimelineEventsDetails = ({ ) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setTimelineDetailsResponse(response.data || []); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -101,7 +100,6 @@ export const useTimelineEventsDetails = ({ setTimelineDetailsRequest((prevRequest) => { const myRequest = { ...(prevRequest ?? {}), - defaultIndex, docValueFields, indexName, eventId, @@ -112,7 +110,7 @@ export const useTimelineEventsDetails = ({ } return prevRequest; }); - }, [defaultIndex, docValueFields, eventId, indexName, skip]); + }, [docValueFields, eventId, indexName, skip]); useEffect(() => { if (timelineDetailsRequest) { diff --git a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx index f340096c75f2b..54db52b985c31 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx @@ -10,14 +10,11 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; import { ESQuery } from '../../../common/typed_json'; -import { IIndexPattern } from '../../../../../../src/plugins/data/public'; - -import { DEFAULT_INDEX_KEY } from '../../../common/constants'; +import { isCompleteResponse, isErrorResponse } from '../../../../../../src/plugins/data/public'; import { inputsModel } from '../../common/store'; import { useKibana } from '../../common/lib/kibana'; import { createFilter } from '../../common/containers/helpers'; import { DocValueFields } from '../../common/containers/query_template'; -import { EventType } from '../../timelines/store/timeline/model'; import { timelineActions } from '../../timelines/store/timeline'; import { detectionsTimelineIds, skipQueryForDetectionsPage } from './helpers'; import { getInspectResponse } from '../../helpers'; @@ -54,15 +51,12 @@ interface UseTimelineEventsProps { filterQuery?: ESQuery | string; skip?: boolean; endDate: string; - eventType?: EventType; id: string; fields: string[]; - indexPattern?: IIndexPattern; - indexToAdd?: string[]; + indexNames: string[]; limit: number; sort: SortField; startDate: string; - canQueryTimeline?: boolean; } const getTimelineEvents = (timelineEdges: TimelineEdges[]): TimelineItem[] => @@ -73,10 +67,8 @@ const ID = 'timelineEventsQuery'; export const useTimelineEvents = ({ docValueFields, endDate, - eventType = 'raw', id = ID, - indexPattern, - indexToAdd = [], + indexNames, fields, filterQuery, startDate, @@ -85,41 +77,37 @@ export const useTimelineEvents = ({ field: '@timestamp', direction: Direction.asc, }, - canQueryTimeline = true, + skip = false, }: UseTimelineEventsProps): [boolean, TimelineArgs] => { const dispatch = useDispatch(); - const { data, notifications, uiSettings } = useKibana().services; + const { data, notifications } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); - const defaultKibanaIndex = uiSettings.get(DEFAULT_INDEX_KEY); - const defaultIndex = - indexPattern == null || (indexPattern != null && indexPattern.title === '') - ? [ - ...(['all', 'raw'].includes(eventType) ? defaultKibanaIndex : []), - ...(['all', 'alert', 'signal'].includes(eventType) ? indexToAdd : []), - ] - : indexPattern?.title.split(',') ?? []; const [loading, setLoading] = useState(false); const [activePage, setActivePage] = useState(0); - const [timelineRequest, setTimelineRequest] = useState({ - fields, - fieldRequested: fields, - filterQuery: createFilter(filterQuery), - id, - timerange: { - interval: '12h', - from: startDate, - to: endDate, - }, - pagination: { - activePage, - querySize: limit, - }, - sort, - defaultIndex, - docValueFields: docValueFields ?? [], - factoryQueryType: TimelineEventsQueries.all, - }); + const [timelineRequest, setTimelineRequest] = useState( + !skip + ? { + fields, + fieldRequested: fields, + filterQuery: createFilter(filterQuery), + id, + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + pagination: { + activePage, + querySize: limit, + }, + sort, + defaultIndex: indexNames, + docValueFields: docValueFields ?? [], + factoryQueryType: TimelineEventsQueries.all, + } + : null + ); const clearSignalsState = useCallback(() => { if (id != null && detectionsTimelineIds.some((timelineId) => timelineId === id)) { @@ -154,7 +142,11 @@ export const useTimelineEvents = ({ }); const timelineSearch = useCallback( - (request: TimelineEventsAllRequestOptions) => { + (request: TimelineEventsAllRequestOptions | null) => { + if (request == null) { + return; + } + let didCancel = false; const asyncSearch = async () => { abortCtrl.current = new AbortController(); @@ -167,7 +159,7 @@ export const useTimelineEvents = ({ }) .subscribe({ next: (response) => { - if (!response.isPartial && !response.isRunning) { + if (isCompleteResponse(response)) { if (!didCancel) { setLoading(false); setTimelineResponse((prevResponse) => ({ @@ -181,7 +173,7 @@ export const useTimelineEvents = ({ })); } searchSubscription$.unsubscribe(); - } else if (response.isPartial && !response.isRunning) { + } else if (isErrorResponse(response)) { if (!didCancel) { setLoading(false); } @@ -211,14 +203,19 @@ export const useTimelineEvents = ({ ); useEffect(() => { - if (!canQueryTimeline || skipQueryForDetectionsPage(id, defaultIndex)) { + if (skip || skipQueryForDetectionsPage(id, indexNames) || indexNames.length === 0) { return; } setTimelineRequest((prevRequest) => { const myRequest = { - ...prevRequest, - defaultIndex, + ...(prevRequest ?? { + fields, + fieldRequested: fields, + id, + factoryQueryType: TimelineEventsQueries.all, + }), + defaultIndex: indexNames, docValueFields: docValueFields ?? [], filterQuery: createFilter(filterQuery), pagination: { @@ -233,8 +230,8 @@ export const useTimelineEvents = ({ sort, }; if ( - canQueryTimeline && - !skipQueryForDetectionsPage(id, defaultIndex) && + !skip && + !skipQueryForDetectionsPage(id, indexNames) && !deepEqual(prevRequest, myRequest) ) { return myRequest; @@ -242,16 +239,17 @@ export const useTimelineEvents = ({ return prevRequest; }); }, [ - defaultIndex, + indexNames, docValueFields, endDate, filterQuery, startDate, - canQueryTimeline, id, activePage, limit, sort, + skip, + fields, ]); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/timelines/containers/one/index.gql_query.ts b/x-pack/plugins/security_solution/public/timelines/containers/one/index.gql_query.ts index 5e50a7fb3313e..b85fbc15ce3f3 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/one/index.gql_query.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/one/index.gql_query.ts @@ -107,6 +107,7 @@ export const oneTimelineQuery = gql` serializedQuery } } + indexNames notes { eventId note diff --git a/x-pack/plugins/security_solution/public/timelines/containers/persist.gql_query.ts b/x-pack/plugins/security_solution/public/timelines/containers/persist.gql_query.ts index c38aa67ccebb2..12d3e6bfd7172 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/persist.gql_query.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/persist.gql_query.ts @@ -95,6 +95,7 @@ export const persistTimelineMutation = gql` serializedQuery } } + indexNames title dateRange { start diff --git a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.test.tsx b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.test.tsx index f9097ddef6490..3c81aa8dac078 100644 --- a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.test.tsx @@ -21,12 +21,12 @@ jest.mock('react-router-dom', () => { }; }); jest.mock('../../overview/components/events_by_dataset'); -jest.mock('../../common/containers/source', () => { - const originalModule = jest.requireActual('../../common/containers/source'); +jest.mock('../../common/containers/sourcerer', () => { + const originalModule = jest.requireActual('../../common/containers/sourcerer'); return { ...originalModule, - useWithSource: jest.fn().mockReturnValue({ + useSourcererScope: jest.fn().mockReturnValue({ indicesExist: true, }), }; diff --git a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx index 79d0f909c7d59..136240939e7a3 100644 --- a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx +++ b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx @@ -15,16 +15,14 @@ import { WrapperPage } from '../../common/components/wrapper_page'; import { useKibana } from '../../common/lib/kibana'; import { SpyRoute } from '../../common/utils/route/spy_routes'; import { useApolloClient } from '../../common/utils/apollo_context'; -import { useWithSource } from '../../common/containers/source'; import { OverviewEmpty } from '../../overview/components/overview_empty'; - import { StatefulOpenTimeline } from '../components/open_timeline'; import { NEW_TEMPLATE_TIMELINE } from '../components/timeline/properties/translations'; import { NewTemplateTimeline } from '../components/timeline/properties/new_template_timeline'; import { NewTimeline } from '../components/timeline/properties/helpers'; - import * as i18n from './translations'; import { SecurityPageName } from '../../app/types'; +import { useSourcererScope } from '../../common/containers/sourcerer'; const TimelinesContainer = styled.div` width: 100%; @@ -38,7 +36,7 @@ export const TimelinesPageComponent: React.FC = () => { const onImportTimelineBtnClick = useCallback(() => { setImportDataModalToggle(true); }, [setImportDataModalToggle]); - const { indicesExist } = useWithSource(); + const { indicesExist } = useSourcererScope(); const apolloClient = useApolloClient(); const capabilitiesCanUserCRUD: boolean = !!useKibana().services.application.capabilities.siem @@ -49,7 +47,7 @@ export const TimelinesPageComponent: React.FC = () => { {indicesExist ? ( <> - + {capabilitiesCanUserCRUD && ( @@ -97,7 +95,7 @@ export const TimelinesPageComponent: React.FC = () => { ) : ( - + )} diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts index da9c363703d16..472e82426468e 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts @@ -15,9 +15,13 @@ import { } from '../../../timelines/components/timeline/data_providers/data_provider'; import { KueryFilterQuery, SerializedFilterQuery } from '../../../common/store/types'; -import { EventType, KqlMode, TimelineModel, ColumnHeaderOptions } from './model'; +import { KqlMode, TimelineModel, ColumnHeaderOptions } from './model'; import { TimelineNonEcsData } from '../../../../common/search_strategy/timeline'; -import { TimelineTypeLiteral, RowRendererId } from '../../../../common/types/timeline'; +import { + TimelineEventsType, + TimelineTypeLiteral, + RowRendererId, +} from '../../../../common/types/timeline'; import { InsertTimeline } from './types'; const actionCreator = actionCreatorFactory('x-pack/security_solution/local/timeline'); @@ -63,6 +67,7 @@ export const createTimeline = actionCreator<{ filters?: Filter[]; columns: ColumnHeaderOptions[]; itemsPerPage?: number; + indexNames: string[]; kqlQuery?: { filterQuery: SerializedFilterQuery | null; filterQueryDraft: KueryFilterQuery | null; @@ -264,7 +269,7 @@ export const clearEventsDeleted = actionCreator<{ id: string; }>('CLEAR_TIMELINE_EVENTS_DELETED'); -export const updateEventType = actionCreator<{ id: string; eventType: EventType }>( +export const updateEventType = actionCreator<{ id: string; eventType: TimelineEventsType }>( 'UPDATE_EVENT_TYPE' ); @@ -272,3 +277,8 @@ export const setExcludedRowRendererIds = actionCreator<{ id: string; excludedRowRendererIds: RowRendererId[]; }>('SET_TIMELINE_EXCLUDED_ROW_RENDERER_IDS'); + +export const updateIndexNames = actionCreator<{ + id: string; + indexNames: string[]; +}>('UPDATE_INDEXES_NAME'); diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts index 7980f62cff171..ce469c2bf57a2 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts @@ -27,6 +27,7 @@ export const timelineDefaults: SubsetTimelineModel & Pick { exists: { field: '@timestamp' }, } as Filter, ], + indexNames: [], isFavorite: false, isLive: false, isSelectAllChecked: false, @@ -272,6 +273,7 @@ describe('Epic Timeline', () => { script: null, }, ], + indexNames: [], kqlMode: 'filter', kqlQuery: { filterQuery: { diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts index ad849c3a995b3..cc8e856de1b16 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts @@ -66,6 +66,7 @@ import { updateRange, updateSort, upsertColumn, + updateIndexNames, updateTimeline, updateTitle, updateAutoSaveMsg, @@ -105,6 +106,7 @@ const timelineActionsType = [ updateDescription.type, updateEventType.type, updateKqlMode.type, + updateIndexNames.type, updateProviders.type, updateSort.type, updateTitle.type, @@ -339,6 +341,7 @@ const timelineInput: TimelineInput = { filters: null, kqlMode: null, kqlQuery: null, + indexNames: null, title: null, timelineType: TimelineType.default, templateTimelineVersion: null, diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx index 8c3f30c75c35b..6507603d30444 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx @@ -84,18 +84,16 @@ describe('epicLocalStorage', () => { dataProviders: mockDataProviders, docValueFields: [], end: endDate, - eventType: 'raw' as TimelineComponentProps['eventType'], filters: [], + indexNames: [], indexPattern, - indexToAdd: [], isLive: false, - isLoadingSource: false, isSaving: false, itemsPerPage: 5, itemsPerPageOptions: [5, 10, 20], kqlMode: 'search' as TimelineComponentProps['kqlMode'], kqlQueryExpression: '', - loadingIndexName: false, + loadingSourcerer: false, onChangeItemsPerPage: jest.fn(), onClose: jest.fn(), onDataProviderEdited: jest.fn(), diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts index 1432e133244d0..fc178df86362b 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts @@ -22,6 +22,7 @@ import { import { KueryFilterQuery, SerializedFilterQuery } from '../../../common/store/model'; import { TimelineNonEcsData } from '../../../../common/search_strategy/timeline'; import { + TimelineEventsType, TimelineTypeLiteral, TimelineType, RowRendererId, @@ -29,7 +30,7 @@ import { import { normalizeTimeRange } from '../../../common/components/url_state/normalize_time_range'; import { timelineDefaults } from './defaults'; -import { ColumnHeaderOptions, KqlMode, TimelineModel, EventType } from './model'; +import { ColumnHeaderOptions, KqlMode, TimelineModel } from './model'; import { TimelineById } from './types'; export const isNotNull = (value: T | null): value is T => value !== null; @@ -139,6 +140,7 @@ interface AddNewTimelineParams { filters?: Filter[]; id: string; itemsPerPage?: number; + indexNames: string[]; kqlQuery?: { filterQuery: SerializedFilterQuery | null; filterQueryDraft: KueryFilterQuery | null; @@ -159,6 +161,7 @@ export const addNewTimeline = ({ filters = timelineDefaults.filters, id, itemsPerPage = timelineDefaults.itemsPerPage, + indexNames, kqlQuery = { filterQuery: null, filterQueryDraft: null }, sort = timelineDefaults.sort, show = false, @@ -186,6 +189,7 @@ export const addNewTimeline = ({ excludedRowRendererIds, filters, itemsPerPage, + indexNames, kqlQuery, sort, show, @@ -667,7 +671,7 @@ export const updateTimelineTitle = ({ interface UpdateTimelineEventTypeParams { id: string; - eventType: EventType; + eventType: TimelineEventsType; timelineById: TimelineById; } diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts index 88ec9da1e0c4e..ec4d37d3b70a2 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts @@ -12,6 +12,7 @@ import { PinnedEvent } from '../../../graphql/types'; import { TimelineNonEcsData } from '../../../../common/search_strategy/timeline'; import { KueryFilterQuery, SerializedFilterQuery } from '../../../common/store/types'; import type { + TimelineEventsType, TimelineType, TimelineStatus, RowRendererId, @@ -19,7 +20,6 @@ import type { export const DEFAULT_PAGE_COUNT = 2; // Eui Pager will not render unless this is a minimum of 2 pages export type KqlMode = 'filter' | 'search'; -export type EventType = 'all' | 'raw' | 'alert' | 'signal'; export type ColumnHeaderType = 'not-filtered' | 'text-filter'; @@ -52,7 +52,7 @@ export interface TimelineModel { /** A summary of the events and notes in this timeline */ description: string; /** Typoe of event you want to see in this timeline */ - eventType?: EventType; + eventType?: TimelineEventsType; /** A map of events in this timeline to the chronologically ordered notes (in this timeline) associated with the event */ eventIdToNoteIds: Record; /** A list of Ids of excluded Row Renderers */ @@ -66,6 +66,8 @@ export interface TimelineModel { highlightedDropAndProviderId: string; /** Uniquely identifies the timeline */ id: string; + /** TO DO sourcerer @X define this */ + indexNames: string[]; /** If selectAll checkbox in header is checked **/ isSelectAllChecked: boolean; /** Events to be rendered as loading **/ @@ -136,6 +138,7 @@ export type SubsetTimelineModel = Readonly< | 'graphEventId' | 'highlightedDropAndProviderId' | 'historyIds' + | 'indexNames' | 'isFavorite' | 'isLive' | 'isSelectAllChecked' diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts index 45bdbd0979276..c2f43625ab464 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts @@ -4,9 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { set } from '@elastic/safer-lodash-set/fp'; import { cloneDeep } from 'lodash/fp'; - import { TimelineType, TimelineStatus } from '../../../../common/types/timeline'; import { @@ -45,77 +43,76 @@ import { updateTimelineTitle, upsertTimelineColumn, } from './helpers'; -import { ColumnHeaderOptions } from './model'; +import { ColumnHeaderOptions, TimelineModel } from './model'; import { timelineDefaults } from './defaults'; import { TimelineById } from './types'; jest.mock('../../../common/components/url_state/normalize_time_range.ts'); -const timelineByIdMock: TimelineById = { - foo: { - dataProviders: [ - { - and: [], - id: '123', - name: 'data provider 1', - enabled: true, - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, - - excluded: false, - kqlQuery: '', - }, - ], - columns: [], - description: '', - deletedEventIds: [], - eventIdToNoteIds: {}, - excludedRowRendererIds: [], - highlightedDropAndProviderId: '', - historyIds: [], - id: 'foo', - savedObjectId: null, - isFavorite: false, - isLive: false, - isSelectAllChecked: false, - isLoading: false, - itemsPerPage: 25, - itemsPerPageOptions: [10, 25, 50], - kqlMode: 'filter', - kqlQuery: { filterQuery: null, filterQueryDraft: null }, - loadingEventIds: [], - title: '', - timelineType: TimelineType.default, - templateTimelineVersion: null, - templateTimelineId: null, - noteIds: [], - pinnedEventIds: {}, - pinnedEventsSaveObject: {}, - dateRange: { - start: '2020-07-07T08:20:18.966Z', - end: '2020-07-08T08:20:18.966Z', - }, - selectedEventIds: {}, - show: true, - showCheckboxes: false, - sort: { - columnId: '@timestamp', - sortDirection: Direction.desc, - }, - status: TimelineStatus.active, - width: DEFAULT_TIMELINE_WIDTH, - isSaving: false, - version: null, +const basicDataProvider: DataProvider = { + and: [], + id: '123', + name: 'data provider 1', + enabled: true, + queryMatch: { + field: '', + value: '', + operator: IS_OPERATOR, }, + excluded: false, + kqlQuery: '', +}; +const basicTimeline: TimelineModel = { + columns: [], + dataProviders: [{ ...basicDataProvider }], + dateRange: { + start: '2020-07-07T08:20:18.966Z', + end: '2020-07-08T08:20:18.966Z', + }, + deletedEventIds: [], + description: '', + eventIdToNoteIds: {}, + excludedRowRendererIds: [], + highlightedDropAndProviderId: '', + historyIds: [], + id: 'foo', + indexNames: [], + isFavorite: false, + isLive: false, + isLoading: false, + isSaving: false, + isSelectAllChecked: false, + itemsPerPage: 25, + itemsPerPageOptions: [10, 25, 50], + kqlMode: 'filter', + kqlQuery: { filterQuery: null, filterQueryDraft: null }, + loadingEventIds: [], + noteIds: [], + pinnedEventIds: {}, + pinnedEventsSaveObject: {}, + savedObjectId: null, + selectedEventIds: {}, + show: true, + showCheckboxes: false, + sort: { + columnId: '@timestamp', + sortDirection: Direction.desc, + }, + status: TimelineStatus.active, + templateTimelineId: null, + templateTimelineVersion: null, + timelineType: TimelineType.default, + title: '', + version: null, + width: DEFAULT_TIMELINE_WIDTH, +}; +const timelineByIdMock: TimelineById = { + foo: { ...basicTimeline }, }; const timelineByIdTemplateMock: TimelineById = { - ...timelineByIdMock, foo: { - ...timelineByIdMock.foo, + ...basicTimeline, timelineType: TimelineType.template, }, }; @@ -132,14 +129,14 @@ describe('Timeline', () => { const update = addTimelineToStore({ id: 'foo', timeline: { - ...timelineByIdMock.foo, + ...basicTimeline, }, timelineById: timelineByIdMock, }); expect(update).toEqual({ foo: { - ...timelineByIdMock.foo, + ...basicTimeline, show: true, }, }); @@ -151,6 +148,7 @@ describe('Timeline', () => { const update = addNewTimeline({ id: 'bar', columns: defaultHeaders, + indexNames: [], timelineById: timelineByIdMock, timelineType: TimelineType.default, }); @@ -161,27 +159,29 @@ describe('Timeline', () => { const update = addNewTimeline({ id: 'bar', columns: timelineDefaults.columns, + indexNames: [], timelineById: timelineByIdMock, timelineType: TimelineType.default, }); expect(update).toEqual({ - foo: timelineByIdMock.foo, - bar: set('id', 'bar', timelineDefaults), + foo: basicTimeline, + bar: { ...timelineDefaults, id: 'bar' }, }); }); test('should add the specified columns to the timeline', () => { - const barWithEmptyColumns = set('id', 'bar', timelineDefaults); - const barWithPopulatedColumns = set('columns', defaultHeaders, barWithEmptyColumns); + const barWithEmptyColumns = { ...timelineDefaults, id: 'bar' }; + const barWithPopulatedColumns = { ...barWithEmptyColumns, columns: defaultHeaders }; const update = addNewTimeline({ id: 'bar', columns: defaultHeaders, + indexNames: [], timelineById: timelineByIdMock, timelineType: TimelineType.default, }); expect(update).toEqual({ - foo: timelineByIdMock.foo, + foo: basicTimeline, bar: barWithPopulatedColumns, }); }); @@ -203,7 +203,14 @@ describe('Timeline', () => { show: false, // value we are changing from true to false timelineById: timelineByIdMock, }); - expect(update).toEqual(set('foo.show', false, timelineByIdMock)); + + expect(update).toEqual({ + ...timelineByIdMock, + foo: { + ...timelineByIdMock.foo, + show: false, + }, + }); }); }); @@ -211,6 +218,7 @@ describe('Timeline', () => { let timelineById: TimelineById = {}; let columns: ColumnHeaderOptions[] = []; let columnToAdd: ColumnHeaderOptions; + let mockWithExistingColumns: TimelineById; beforeEach(() => { timelineById = cloneDeep(timelineByIdMock); @@ -226,6 +234,13 @@ describe('Timeline', () => { aggregatable: true, width: DEFAULT_COLUMN_MIN_WIDTH, }; + mockWithExistingColumns = { + ...timelineById, + foo: { + ...timelineById.foo, + columns, + }, + }; }); test('should return a new reference and not the same reference', () => { @@ -248,12 +263,11 @@ describe('Timeline', () => { timelineById, }); - expect(update).toEqual(set('foo.columns', expectedColumns, timelineById)); + expect(update.foo.columns).toEqual(expectedColumns); }); test('should add a new column to an existing collection of columns at the beginning of the collection', () => { const expectedColumns = [columnToAdd, ...columns]; - const mockWithExistingColumns = set('foo.columns', columns, timelineById); const update = upsertTimelineColumn({ column: columnToAdd, @@ -261,13 +275,11 @@ describe('Timeline', () => { index: 0, timelineById: mockWithExistingColumns, }); - - expect(update).toEqual(set('foo.columns', expectedColumns, mockWithExistingColumns)); + expect(update.foo.columns).toEqual(expectedColumns); }); test('should add a new column to an existing collection of columns in the middle of the collection', () => { const expectedColumns = [columns[0], columnToAdd, columns[1], columns[2]]; - const mockWithExistingColumns = set('foo.columns', columns, timelineById); const update = upsertTimelineColumn({ column: columnToAdd, @@ -276,12 +288,11 @@ describe('Timeline', () => { timelineById: mockWithExistingColumns, }); - expect(update).toEqual(set('foo.columns', expectedColumns, mockWithExistingColumns)); + expect(update.foo.columns).toEqual(expectedColumns); }); test('should add a new column to an existing collection of columns at the end of the collection', () => { const expectedColumns = [...columns, columnToAdd]; - const mockWithExistingColumns = set('foo.columns', columns, timelineById); const update = upsertTimelineColumn({ column: columnToAdd, @@ -290,13 +301,11 @@ describe('Timeline', () => { timelineById: mockWithExistingColumns, }); - expect(update).toEqual(set('foo.columns', expectedColumns, mockWithExistingColumns)); + expect(update.foo.columns).toEqual(expectedColumns); }); columns.forEach((column, i) => { test(`should upsert (NOT add a new column) a column when already exists at the same index (${i})`, () => { - const mockWithExistingColumns = set('foo.columns', columns, timelineById); - const update = upsertTimelineColumn({ column, id: 'foo', @@ -304,13 +313,12 @@ describe('Timeline', () => { timelineById: mockWithExistingColumns, }); - expect(update).toEqual(set('foo.columns', columns, mockWithExistingColumns)); + expect(update.foo.columns).toEqual(columns); }); }); test('should allow the 1st column to be moved to the 2nd column', () => { const expectedColumns = [columns[1], columns[0], columns[2]]; - const mockWithExistingColumns = set('foo.columns', columns, timelineById); const update = upsertTimelineColumn({ column: columns[0], @@ -319,12 +327,11 @@ describe('Timeline', () => { timelineById: mockWithExistingColumns, }); - expect(update).toEqual(set('foo.columns', expectedColumns, mockWithExistingColumns)); + expect(update.foo.columns).toEqual(expectedColumns); }); test('should allow the 1st column to be moved to the 3rd column', () => { const expectedColumns = [columns[1], columns[2], columns[0]]; - const mockWithExistingColumns = set('foo.columns', columns, timelineById); const update = upsertTimelineColumn({ column: columns[0], @@ -333,12 +340,11 @@ describe('Timeline', () => { timelineById: mockWithExistingColumns, }); - expect(update).toEqual(set('foo.columns', expectedColumns, mockWithExistingColumns)); + expect(update.foo.columns).toEqual(expectedColumns); }); test('should allow the 2nd column to be moved to the 1st column', () => { const expectedColumns = [columns[1], columns[0], columns[2]]; - const mockWithExistingColumns = set('foo.columns', columns, timelineById); const update = upsertTimelineColumn({ column: columns[1], @@ -347,12 +353,11 @@ describe('Timeline', () => { timelineById: mockWithExistingColumns, }); - expect(update).toEqual(set('foo.columns', expectedColumns, mockWithExistingColumns)); + expect(update.foo.columns).toEqual(expectedColumns); }); test('should allow the 2nd column to be moved to the 3rd column', () => { const expectedColumns = [columns[0], columns[2], columns[1]]; - const mockWithExistingColumns = set('foo.columns', columns, timelineById); const update = upsertTimelineColumn({ column: columns[1], @@ -361,12 +366,11 @@ describe('Timeline', () => { timelineById: mockWithExistingColumns, }); - expect(update).toEqual(set('foo.columns', expectedColumns, mockWithExistingColumns)); + expect(update.foo.columns).toEqual(expectedColumns); }); test('should allow the 3rd column to be moved to the 1st column', () => { const expectedColumns = [columns[2], columns[0], columns[1]]; - const mockWithExistingColumns = set('foo.columns', columns, timelineById); const update = upsertTimelineColumn({ column: columns[2], @@ -375,12 +379,11 @@ describe('Timeline', () => { timelineById: mockWithExistingColumns, }); - expect(update).toEqual(set('foo.columns', expectedColumns, mockWithExistingColumns)); + expect(update.foo.columns).toEqual(expectedColumns); }); test('should allow the 3rd column to be moved to the 2nd column', () => { const expectedColumns = [columns[0], columns[2], columns[1]]; - const mockWithExistingColumns = set('foo.columns', columns, timelineById); const update = upsertTimelineColumn({ column: columns[2], @@ -389,75 +392,39 @@ describe('Timeline', () => { timelineById: mockWithExistingColumns, }); - expect(update).toEqual(set('foo.columns', expectedColumns, mockWithExistingColumns)); + expect(update.foo.columns).toEqual(expectedColumns); }); }); describe('#addTimelineProvider', () => { + const providerToAdd: DataProvider = { + ...basicDataProvider, + id: '567', + name: 'data provider 2', + }; test('should return a new reference and not the same reference', () => { const update = addTimelineProvider({ id: 'foo', - provider: { - and: [], - id: '567', - name: 'data provider 2', - enabled: true, - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, - - excluded: false, - kqlQuery: '', - }, + provider: providerToAdd, timelineById: timelineByIdMock, }); expect(update).not.toBe(timelineByIdMock); }); test('should add a new timeline provider', () => { - const providerToAdd: DataProvider = { - and: [], - id: '567', - name: 'data provider 2', - enabled: true, - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, - - excluded: false, - kqlQuery: '', - }; const update = addTimelineProvider({ id: 'foo', provider: providerToAdd, timelineById: timelineByIdMock, }); - const addedDataProvider = timelineByIdMock.foo.dataProviders.concat(providerToAdd); - expect(update).toEqual(set('foo.dataProviders', addedDataProvider, timelineByIdMock)); + const addedDataProvider = [...basicTimeline.dataProviders].concat(providerToAdd); + expect(update.foo.dataProviders).toEqual(addedDataProvider); }); test('should NOT add a new timeline provider if it already exists and the attributes "and" is empty', () => { - const providerToAdd: DataProvider = { - and: [], - id: '123', - name: 'data provider 1', - enabled: true, - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, - - excluded: false, - kqlQuery: '', - }; const update = addTimelineProvider({ id: 'foo', - provider: providerToAdd, + provider: basicDataProvider, timelineById: timelineByIdMock, }); expect(update).toEqual(timelineByIdMock); @@ -467,68 +434,45 @@ describe('Timeline', () => { const myMockTimelineByIdMock = cloneDeep(timelineByIdMock); myMockTimelineByIdMock.foo.dataProviders[0].and = [ { + ...basicDataProvider, id: '456', name: 'and data provider 1', - enabled: true, - excluded: false, - kqlQuery: '', - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, }, ]; - const providerToAdd: DataProvider = { - and: [], - id: '123', - name: 'data provider 1', - enabled: true, - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, - - excluded: false, - kqlQuery: '', - }; + const provider = { ...basicDataProvider }; const update = addTimelineProvider({ id: 'foo', - provider: providerToAdd, + provider, timelineById: myMockTimelineByIdMock, }); - expect(update).toEqual(set('foo.dataProviders[1]', providerToAdd, myMockTimelineByIdMock)); + expect(update.foo.dataProviders[1]).toEqual(provider); }); test('should UPSERT an existing timeline provider if it already exists', () => { - const providerToAdd: DataProvider = { - and: [], - id: '123', - name: 'my name changed', - enabled: true, - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, - excluded: false, - kqlQuery: '', - }; const update = addTimelineProvider({ id: 'foo', - provider: providerToAdd, + provider: { + ...basicDataProvider, + name: 'my name changed', + }, timelineById: timelineByIdMock, }); - expect(update).toEqual(set('foo.dataProviders[0].name', 'my name changed', timelineByIdMock)); + expect(update.foo.dataProviders[0].name).toEqual('my name changed'); }); }); describe('#removeTimelineColumn', () => { + let mockWithExistingColumns: TimelineById; + beforeEach(() => { + mockWithExistingColumns = { + ...timelineByIdMock, + foo: { + ...timelineByIdMock.foo, + columns: columnsMock, + }, + }; + }); test('should return a new reference and not the same reference', () => { - // pre-populate a new mock with existing columns: - const mockWithExistingColumns = set('foo.columns', columnsMock, timelineByIdMock); - const update = removeTimelineColumn({ id: 'foo', columnId: columnsMock[0].id, @@ -541,70 +485,65 @@ describe('Timeline', () => { test('should remove just the first column when the id matches', () => { const expectedColumns = [columnsMock[1], columnsMock[2]]; - // pre-populate a new mock with existing columns: - const mockWithExistingColumns = set('foo.columns', columnsMock, timelineByIdMock); - const update = removeTimelineColumn({ id: 'foo', columnId: columnsMock[0].id, timelineById: mockWithExistingColumns, }); - expect(update).toEqual(set('foo.columns', expectedColumns, mockWithExistingColumns)); + expect(update.foo.columns).toEqual(expectedColumns); }); test('should remove just the last column when the id matches', () => { const expectedColumns = [columnsMock[0], columnsMock[1]]; - // pre-populate a new mock with existing columns: - const mockWithExistingColumns = set('foo.columns', columnsMock, timelineByIdMock); - const update = removeTimelineColumn({ id: 'foo', columnId: columnsMock[2].id, timelineById: mockWithExistingColumns, }); - expect(update).toEqual(set('foo.columns', expectedColumns, mockWithExistingColumns)); + expect(update.foo.columns).toEqual(expectedColumns); }); test('should remove just the middle column when the id matches', () => { const expectedColumns = [columnsMock[0], columnsMock[2]]; - // pre-populate a new mock with existing columns: - const mockWithExistingColumns = set('foo.columns', columnsMock, timelineByIdMock); - const update = removeTimelineColumn({ id: 'foo', columnId: columnsMock[1].id, timelineById: mockWithExistingColumns, }); - expect(update).toEqual(set('foo.columns', expectedColumns, mockWithExistingColumns)); + expect(update.foo.columns).toEqual(expectedColumns); }); test('should not modify the columns if the id to remove was not found', () => { const expectedColumns = cloneDeep(columnsMock); - // pre-populate a new mock with existing columns: - const mockWithExistingColumns = set('foo.columns', columnsMock, timelineByIdMock); - const update = removeTimelineColumn({ id: 'foo', columnId: 'does.not.exist', timelineById: mockWithExistingColumns, }); - expect(update).toEqual(set('foo.columns', expectedColumns, mockWithExistingColumns)); + expect(update.foo.columns).toEqual(expectedColumns); }); }); describe('#applyDeltaToColumnWidth', () => { + let mockWithExistingColumns: TimelineById; + beforeEach(() => { + mockWithExistingColumns = { + ...timelineByIdMock, + foo: { + ...timelineByIdMock.foo, + columns: columnsMock, + }, + }; + }); test('should return a new reference and not the same reference', () => { const delta = 50; - // pre-populate a new mock with existing columns: - const mockWithExistingColumns = set('foo.columns', columnsMock, timelineByIdMock); - const update = applyDeltaToTimelineColumnWidth({ id: 'foo', columnId: columnsMock[0].id, @@ -624,9 +563,6 @@ describe('Timeline', () => { }; const expectedColumns = [expectedToHaveNewWidth, columnsMock[1], columnsMock[2]]; - // pre-populate a new mock with existing columns: - const mockWithExistingColumns = set('foo.columns', columnsMock, timelineByIdMock); - const update = applyDeltaToTimelineColumnWidth({ id: 'foo', columnId: aDateColumn.id, @@ -634,7 +570,7 @@ describe('Timeline', () => { timelineById: mockWithExistingColumns, }); - expect(update).toEqual(set('foo.columns', expectedColumns, mockWithExistingColumns)); + expect(update.foo.columns).toEqual(expectedColumns); }); test('should NOT update (just) the specified column of type `date` when the id matches, because the result of applying the delta is less than the min width for a date column', () => { @@ -646,9 +582,6 @@ describe('Timeline', () => { }; const expectedColumns = [expectedToHaveNewWidth, columnsMock[1], columnsMock[2]]; - // pre-populate a new mock with existing columns: - const mockWithExistingColumns = set('foo.columns', columnsMock, timelineByIdMock); - const update = applyDeltaToTimelineColumnWidth({ id: 'foo', columnId: aDateColumn.id, @@ -656,7 +589,7 @@ describe('Timeline', () => { timelineById: mockWithExistingColumns, }); - expect(update).toEqual(set('foo.columns', expectedColumns, mockWithExistingColumns)); + expect(update.foo.columns).toEqual(expectedColumns); }); test('should update (just) the specified non-date column when the id matches, and the result of applying the delta is greater than the min width for the column', () => { @@ -668,9 +601,6 @@ describe('Timeline', () => { }; const expectedColumns = [columnsMock[0], expectedToHaveNewWidth, columnsMock[2]]; - // pre-populate a new mock with existing columns: - const mockWithExistingColumns = set('foo.columns', columnsMock, timelineByIdMock); - const update = applyDeltaToTimelineColumnWidth({ id: 'foo', columnId: aNonDateColumn.id, @@ -678,7 +608,7 @@ describe('Timeline', () => { timelineById: mockWithExistingColumns, }); - expect(update).toEqual(set('foo.columns', expectedColumns, mockWithExistingColumns)); + expect(update.foo.columns).toEqual(expectedColumns); }); test('should NOT update the specified non-date column when the id matches, because the result of applying the delta is less than the min width for the column', () => { @@ -690,9 +620,6 @@ describe('Timeline', () => { }; const expectedColumns = [columnsMock[0], expectedToHaveNewWidth, columnsMock[2]]; - // pre-populate a new mock with existing columns: - const mockWithExistingColumns = set('foo.columns', columnsMock, timelineByIdMock); - const update = applyDeltaToTimelineColumnWidth({ id: 'foo', columnId: aNonDateColumn.id, @@ -700,24 +627,21 @@ describe('Timeline', () => { timelineById: mockWithExistingColumns, }); - expect(update).toEqual(set('foo.columns', expectedColumns, mockWithExistingColumns)); + expect(update.foo.columns).toEqual(expectedColumns); }); }); describe('#addAndProviderToTimelineProvider', () => { test('should add a new and provider to an existing timeline provider', () => { const providerToAdd: DataProvider = { - and: [], + ...basicDataProvider, id: '567', name: 'data provider 2', - enabled: true, queryMatch: { field: 'handsome', - value: 'garrett', + value: 'xavier', operator: IS_OPERATOR, }, - excluded: false, - kqlQuery: '', }; const newTimeline = addTimelineProvider({ @@ -729,18 +653,14 @@ describe('Timeline', () => { newTimeline.foo.highlightedDropAndProviderId = '567'; const andProviderToAdd: DataProvider = { - and: [], + ...basicDataProvider, id: '568', name: 'And Data Provider', - enabled: true, queryMatch: { field: 'smart', - value: 'frank', + value: 'steph', operator: IS_OPERATOR, }, - - excluded: false, - kqlQuery: '', }; const update = addTimelineProvider({ @@ -757,30 +677,25 @@ describe('Timeline', () => { test('should add another and provider because it is not a duplicate', () => { const providerToAdd: DataProvider = { + ...basicDataProvider, and: [ { + ...basicDataProvider, id: '568', name: 'And Data Provider', - enabled: true, queryMatch: { field: 'smart', - value: 'garrett', + value: 'xavier', operator: IS_OPERATOR, }, - excluded: false, - kqlQuery: '', }, ], id: '567', - name: 'data provider 1', - enabled: true, queryMatch: { field: 'handsome', - value: 'frank', + value: 'steph', operator: IS_OPERATOR, }, - excluded: false, - kqlQuery: '', }; const newTimeline = addTimelineProvider({ @@ -792,17 +707,14 @@ describe('Timeline', () => { newTimeline.foo.highlightedDropAndProviderId = '567'; const andProviderToAdd: DataProvider = { - and: [], + ...basicDataProvider, id: '569', name: 'And Data Provider', - enabled: true, queryMatch: { field: 'happy', value: 'andrewG', operator: IS_OPERATOR, }, - excluded: false, - kqlQuery: '', }; // temporary, we will have to decouple DataProvider & DataProvidersAnd // that's bigger a refactor than just fixing a bug @@ -814,36 +726,31 @@ describe('Timeline', () => { timelineById: newTimeline, }); - expect(update).toEqual(set('foo.dataProviders[1].and[1]', andProviderToAdd, newTimeline)); + expect(update.foo.dataProviders[1].and[1]).toEqual(andProviderToAdd); newTimeline.foo.highlightedDropAndProviderId = ''; }); test('should NOT add another and provider because it is a duplicate', () => { const providerToAdd: DataProvider = { + ...basicDataProvider, and: [ { + ...basicDataProvider, id: '568', name: 'And Data Provider', - enabled: true, queryMatch: { field: 'smart', - value: 'garrett', + value: 'xavier', operator: IS_OPERATOR, }, - excluded: false, - kqlQuery: '', }, ], id: '567', - name: 'data provider 1', - enabled: true, queryMatch: { field: 'handsome', - value: 'frank', + value: 'steph', operator: IS_OPERATOR, }, - excluded: false, - kqlQuery: '', }; const newTimeline = addTimelineProvider({ @@ -855,17 +762,14 @@ describe('Timeline', () => { newTimeline.foo.highlightedDropAndProviderId = '567'; const andProviderToAdd: DataProvider = { - and: [], + ...basicDataProvider, id: '569', name: 'And Data Provider', - enabled: true, queryMatch: { field: 'smart', - value: 'garrett', + value: 'xavier', operator: IS_OPERATOR, }, - excluded: false, - kqlQuery: '', }; const update = addTimelineProvider({ id: 'foo', @@ -894,7 +798,7 @@ describe('Timeline', () => { columns: columnsMock, timelineById: timelineByIdMock, }); - expect(update).toEqual(set('foo.columns', [...columnsMock], timelineByIdMock)); + expect(update.foo.columns).toEqual([...columnsMock]); }); }); @@ -916,7 +820,7 @@ describe('Timeline', () => { description: newDescription, timelineById: timelineByIdMock, }); - expect(update).toEqual(set('foo.description', newDescription, timelineByIdMock)); + expect(update.foo.description).toEqual(newDescription); }); test('should always trim all leading whitespace and allow only one trailing space', () => { @@ -925,7 +829,7 @@ describe('Timeline', () => { description: ' breathing room ', timelineById: timelineByIdMock, }); - expect(update).toEqual(set('foo.description', 'breathing room ', timelineByIdMock)); + expect(update.foo.description).toEqual('breathing room '); }); }); @@ -947,7 +851,7 @@ describe('Timeline', () => { title: newTitle, timelineById: timelineByIdMock, }); - expect(update).toEqual(set('foo.title', newTitle, timelineByIdMock)); + expect(update.foo.title).toEqual(newTitle); }); test('should always trim all leading whitespace and allow only one trailing space', () => { @@ -956,7 +860,7 @@ describe('Timeline', () => { title: ' room at the back ', timelineById: timelineByIdMock, }); - expect(update).toEqual(set('foo.title', 'room at the back ', timelineByIdMock)); + expect(update.foo.title).toEqual('room at the back '); }); }); @@ -966,18 +870,9 @@ describe('Timeline', () => { id: 'foo', providers: [ { - and: [], + ...basicDataProvider, id: '567', name: 'data provider 2', - enabled: true, - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, - - excluded: false, - kqlQuery: '', }, ], timelineById: timelineByIdMock, @@ -985,64 +880,47 @@ describe('Timeline', () => { expect(update).not.toBe(timelineByIdMock); }); - test('should add update a timeline with new providers', () => { + test('should add update a timeline with new providers BBB', () => { const providerToAdd: DataProvider = { - and: [], + ...basicDataProvider, id: '567', name: 'data provider 2', - enabled: true, - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, - - excluded: false, - kqlQuery: '', }; const update = updateTimelineProviders({ id: 'foo', providers: [providerToAdd], timelineById: timelineByIdMock, }); - expect(update).toEqual(set('foo.dataProviders', [providerToAdd], timelineByIdMock)); + expect(update.foo.dataProviders).toEqual([providerToAdd]); }); }); describe('#updateTimelineRange', () => { - test('should return a new reference and not the same reference', () => { - const update = updateTimelineRange({ + let update: TimelineById; + beforeAll(() => { + update = updateTimelineRange({ id: 'foo', start: '2020-07-07T08:20:18.966Z', end: '2020-07-08T08:20:18.966Z', timelineById: timelineByIdMock, }); + }); + test('should return a new reference and not the same reference', () => { expect(update).not.toBe(timelineByIdMock); }); test('should update the timeline range', () => { - const update = updateTimelineRange({ - id: 'foo', + expect(update.foo.dateRange).toEqual({ start: '2020-07-07T08:20:18.966Z', end: '2020-07-08T08:20:18.966Z', - timelineById: timelineByIdMock, }); - expect(update).toEqual( - set( - 'foo.dateRange', - { - start: '2020-07-07T08:20:18.966Z', - end: '2020-07-08T08:20:18.966Z', - }, - timelineByIdMock - ) - ); }); }); describe('#updateTimelineSort', () => { - test('should return a new reference and not the same reference', () => { - const update = updateTimelineSort({ + let update: TimelineById; + beforeAll(() => { + update = updateTimelineSort({ id: 'foo', sort: { columnId: 'some column', @@ -1050,31 +928,19 @@ describe('Timeline', () => { }, timelineById: timelineByIdMock, }); + }); + test('should return a new reference and not the same reference', () => { expect(update).not.toBe(timelineByIdMock); }); test('should update the timeline range', () => { - const update = updateTimelineSort({ - id: 'foo', - sort: { - columnId: 'some column', - sortDirection: Direction.desc, - }, - timelineById: timelineByIdMock, - }); - expect(update).toEqual( - set( - 'foo.sort', - { columnId: 'some column', sortDirection: Direction.desc }, - timelineByIdMock - ) - ); + expect(update.foo.sort).toEqual({ columnId: 'some column', sortDirection: Direction.desc }); }); }); describe('#updateTimelineProviderEnabled', () => { test('should return a new reference and not the same reference', () => { - const update = updateTimelineProviderEnabled({ + const update: TimelineById = updateTimelineProviderEnabled({ id: 'foo', providerId: '123', enabled: false, // value we are updating from true to false @@ -1084,17 +950,17 @@ describe('Timeline', () => { }); test('should return a new reference for data provider and not the same reference of data provider', () => { - const update = updateTimelineProviderEnabled({ + const update: TimelineById = updateTimelineProviderEnabled({ id: 'foo', providerId: '123', enabled: false, // value we are updating from true to false timelineById: timelineByIdMock, }); - expect(update.foo.dataProviders).not.toBe(timelineByIdMock.foo.dataProviders); + expect(update.foo.dataProviders).not.toBe(basicTimeline.dataProviders); }); test('should update the timeline provider enabled from true to false', () => { - const update = updateTimelineProviderEnabled({ + const update: TimelineById = updateTimelineProviderEnabled({ id: 'foo', providerId: '123', enabled: false, // value we are updating from true to false @@ -1102,81 +968,29 @@ describe('Timeline', () => { }); const expected: TimelineById = { foo: { - id: 'foo', - savedObjectId: null, - columns: [], + ...basicTimeline, dataProviders: [ { - and: [], - id: '123', - name: 'data provider 1', - enabled: false, // This value changed from true to false - excluded: false, - kqlQuery: '', - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, + ...basicDataProvider, + enabled: false, }, ], - deletedEventIds: [], - description: '', - eventIdToNoteIds: {}, - excludedRowRendererIds: [], - highlightedDropAndProviderId: '', - historyIds: [], - isFavorite: false, - isLive: false, - isSelectAllChecked: false, - isLoading: false, - kqlMode: 'filter', - kqlQuery: { filterQuery: null, filterQueryDraft: null }, - loadingEventIds: [], - title: '', - timelineType: TimelineType.default, - templateTimelineVersion: null, - templateTimelineId: null, - noteIds: [], - dateRange: { - start: '2020-07-07T08:20:18.966Z', - end: '2020-07-08T08:20:18.966Z', - }, - selectedEventIds: {}, - show: true, - showCheckboxes: false, - sort: { - columnId: '@timestamp', - sortDirection: Direction.desc, - }, - status: TimelineStatus.active, - pinnedEventIds: {}, - pinnedEventsSaveObject: {}, - itemsPerPage: 25, - itemsPerPageOptions: [10, 25, 50], - width: DEFAULT_TIMELINE_WIDTH, - isSaving: false, - version: null, }, }; expect(update).toEqual(expected); }); test('should update only one data provider and not two data providers', () => { - const multiDataProvider = timelineByIdMock.foo.dataProviders.concat({ - and: [], + const multiDataProvider = [...basicTimeline.dataProviders].concat({ + ...basicDataProvider, id: '456', - name: 'data provider 1', - enabled: true, - excluded: false, - kqlQuery: '', - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, }); - const multiDataProviderMock = set('foo.dataProviders', multiDataProvider, timelineByIdMock); + const multiDataProviderMock = { + foo: { + ...timelineByIdMock.foo, + dataProviders: multiDataProvider, + }, + }; const update = updateTimelineProviderEnabled({ id: 'foo', providerId: '123', @@ -1185,74 +999,17 @@ describe('Timeline', () => { }); const expected: TimelineById = { foo: { - id: 'foo', - savedObjectId: null, - columns: [], + ...basicTimeline, dataProviders: [ { - and: [], - id: '123', - name: 'data provider 1', - enabled: false, // value we are updating from true to false - excluded: false, - kqlQuery: '', - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, + ...basicDataProvider, + enabled: false, }, { - and: [], + ...basicDataProvider, id: '456', - name: 'data provider 1', - enabled: true, - excluded: false, - kqlQuery: '', - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, }, ], - description: '', - deletedEventIds: [], - eventIdToNoteIds: {}, - excludedRowRendererIds: [], - highlightedDropAndProviderId: '', - historyIds: [], - isFavorite: false, - isLive: false, - isSelectAllChecked: false, - isLoading: false, - kqlMode: 'filter', - kqlQuery: { filterQuery: null, filterQueryDraft: null }, - loadingEventIds: [], - title: '', - timelineType: TimelineType.default, - templateTimelineVersion: null, - templateTimelineId: null, - noteIds: [], - dateRange: { - start: '2020-07-07T08:20:18.966Z', - end: '2020-07-08T08:20:18.966Z', - }, - selectedEventIds: {}, - show: true, - showCheckboxes: false, - sort: { - columnId: '@timestamp', - sortDirection: Direction.desc, - }, - status: TimelineStatus.active, - pinnedEventIds: {}, - pinnedEventsSaveObject: {}, - itemsPerPage: 25, - itemsPerPageOptions: [10, 25, 50], - width: DEFAULT_TIMELINE_WIDTH, - isSaving: false, - version: null, }, }; expect(update).toEqual(expected); @@ -1261,34 +1018,18 @@ describe('Timeline', () => { describe('#updateTimelineAndProviderEnabled', () => { let timelineByIdwithAndMock: TimelineById = timelineByIdMock; + let update: TimelineById; beforeEach(() => { const providerToAdd: DataProvider = { + ...basicDataProvider, and: [ { + ...basicDataProvider, id: '568', name: 'And Data Provider', - enabled: true, - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, - - excluded: false, - kqlQuery: '', }, ], id: '567', - name: 'data provider 1', - enabled: true, - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, - - excluded: false, - kqlQuery: '', }; timelineByIdwithAndMock = addTimelineProvider({ @@ -1296,43 +1037,30 @@ describe('Timeline', () => { provider: providerToAdd, timelineById: timelineByIdMock, }); - }); - test('should return a new reference and not the same reference', () => { - const update = updateTimelineProviderEnabled({ + update = updateTimelineProviderEnabled({ id: 'foo', providerId: '567', enabled: false, // value we are updating from true to false timelineById: timelineByIdwithAndMock, andProviderId: '568', }); + }); + + test('should return a new reference and not the same reference', () => { expect(update).not.toBe(timelineByIdwithAndMock); }); test('should return a new reference for and data provider and not the same reference of data and provider', () => { - const update = updateTimelineProviderEnabled({ - id: 'foo', - providerId: '567', - enabled: false, // value we are updating from true to false - timelineById: timelineByIdwithAndMock, - andProviderId: '568', - }); - expect(update.foo.dataProviders).not.toBe(timelineByIdMock.foo.dataProviders); + expect(update.foo.dataProviders).not.toBe(basicTimeline.dataProviders); }); test('should update the timeline and provider enabled from true to false', () => { - const update = updateTimelineProviderEnabled({ - id: 'foo', - providerId: '567', - enabled: false, // value we are updating from true to false - timelineById: timelineByIdwithAndMock, - andProviderId: '568', - }); const indexProvider = update.foo.dataProviders.findIndex((i) => i.id === '567'); expect(update.foo.dataProviders[indexProvider].and[0].enabled).toEqual(false); }); - test('should update only one and data provider and not two and data providers', () => { + test('should update only one and data provider and not two and data providers ahhhh', () => { const indexProvider = timelineByIdwithAndMock.foo.dataProviders.findIndex( (i) => i.id === '567' ); @@ -1351,12 +1079,9 @@ describe('Timeline', () => { excluded: false, kqlQuery: '', }); - const multiAndDataProviderMock = set( - `foo.dataProviders[${indexProvider}].and`, - multiAndDataProvider, - timelineByIdwithAndMock - ); - const update = updateTimelineProviderEnabled({ + const multiAndDataProviderMock = timelineByIdwithAndMock; + multiAndDataProviderMock.foo.dataProviders[indexProvider].and = multiAndDataProvider; + update = updateTimelineProviderEnabled({ id: 'foo', providerId: '567', enabled: false, // value we are updating from true to false @@ -1375,111 +1100,51 @@ describe('Timeline', () => { }); describe('#updateTimelineProviderExcluded', () => { - test('should return a new reference and not the same reference', () => { - const update = updateTimelineProviderExcluded({ + let update: TimelineById; + beforeAll(() => { + update = updateTimelineProviderExcluded({ id: 'foo', providerId: '123', excluded: true, // value we are updating from false to true timelineById: timelineByIdMock, }); + }); + test('should return a new reference and not the same reference', () => { expect(update).not.toBe(timelineByIdMock); }); test('should return a new reference for data provider and not the same reference of data provider', () => { - const update = updateTimelineProviderExcluded({ - id: 'foo', - providerId: '123', - excluded: true, // value we are updating from false to true - timelineById: timelineByIdMock, - }); - expect(update.foo.dataProviders).not.toBe(timelineByIdMock.foo.dataProviders); + expect(update.foo.dataProviders).not.toBe(basicTimeline.dataProviders); }); test('should update the timeline provider excluded from true to false', () => { - const update = updateTimelineProviderExcluded({ - id: 'foo', - providerId: '123', - excluded: true, // value we are updating from false to true - timelineById: timelineByIdMock, - }); const expected: TimelineById = { foo: { - id: 'foo', - savedObjectId: null, - columns: [], + ...basicTimeline, dataProviders: [ { - and: [], - id: '123', - name: 'data provider 1', - enabled: true, - excluded: true, // This value changed from true to false - kqlQuery: '', - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, + ...basicDataProvider, + excluded: true, }, ], - description: '', - deletedEventIds: [], - eventIdToNoteIds: {}, - excludedRowRendererIds: [], - highlightedDropAndProviderId: '', - historyIds: [], - isFavorite: false, - isLive: false, - isSelectAllChecked: false, - isLoading: false, - kqlMode: 'filter', - kqlQuery: { filterQuery: null, filterQueryDraft: null }, - loadingEventIds: [], - title: '', - timelineType: TimelineType.default, - templateTimelineVersion: null, - templateTimelineId: null, - noteIds: [], - dateRange: { - start: '2020-07-07T08:20:18.966Z', - end: '2020-07-08T08:20:18.966Z', - }, - selectedEventIds: {}, - show: true, - showCheckboxes: false, - sort: { - columnId: '@timestamp', - sortDirection: Direction.desc, - }, - status: TimelineStatus.active, - pinnedEventIds: {}, - pinnedEventsSaveObject: {}, - itemsPerPage: 25, - itemsPerPageOptions: [10, 25, 50], - width: DEFAULT_TIMELINE_WIDTH, - isSaving: false, - version: null, }, }; expect(update).toEqual(expected); }); test('should update only one data provider and not two data providers', () => { - const multiDataProvider = timelineByIdMock.foo.dataProviders.concat({ - and: [], + const multiDataProvider = basicTimeline.dataProviders.concat({ + ...basicDataProvider, id: '456', - name: 'data provider 1', - enabled: true, - excluded: false, - kqlQuery: '', - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, }); - const multiDataProviderMock = set('foo.dataProviders', multiDataProvider, timelineByIdMock); - const update = updateTimelineProviderExcluded({ + const multiDataProviderMock = { + ...timelineByIdMock, + foo: { + ...timelineByIdMock.foo, + dataProviders: multiDataProvider, + }, + }; + update = updateTimelineProviderExcluded({ id: 'foo', providerId: '123', excluded: true, // value we are updating from false to true @@ -1487,74 +1152,17 @@ describe('Timeline', () => { }); const expected: TimelineById = { foo: { - id: 'foo', - savedObjectId: null, - columns: [], + ...basicTimeline, dataProviders: [ { - and: [], - id: '123', - name: 'data provider 1', - enabled: true, + ...basicDataProvider, excluded: true, // value we are updating from false to true - kqlQuery: '', - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, }, { - and: [], + ...basicDataProvider, id: '456', - name: 'data provider 1', - enabled: true, - excluded: false, - kqlQuery: '', - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, }, ], - description: '', - deletedEventIds: [], - eventIdToNoteIds: {}, - excludedRowRendererIds: [], - highlightedDropAndProviderId: '', - historyIds: [], - isFavorite: false, - isLive: false, - isSelectAllChecked: false, - isLoading: false, - kqlMode: 'filter', - kqlQuery: { filterQuery: null, filterQueryDraft: null }, - loadingEventIds: [], - title: '', - timelineType: TimelineType.default, - templateTimelineId: null, - templateTimelineVersion: null, - noteIds: [], - dateRange: { - start: '2020-07-07T08:20:18.966Z', - end: '2020-07-08T08:20:18.966Z', - }, - selectedEventIds: {}, - show: true, - showCheckboxes: false, - sort: { - columnId: '@timestamp', - sortDirection: Direction.desc, - }, - status: TimelineStatus.active, - pinnedEventIds: {}, - pinnedEventsSaveObject: {}, - itemsPerPage: 25, - itemsPerPageOptions: [10, 25, 50], - width: DEFAULT_TIMELINE_WIDTH, - isSaving: false, - version: null, }, }; expect(update).toEqual(expected); @@ -1596,173 +1204,71 @@ describe('Timeline', () => { const update = updateTimelineProviderType({ id: 'foo', providerId: '123', - type: DataProviderType.template, // value we are updating from default to template + type: DataProviderType.template, timelineById: timelineByIdTemplateMock, }); const expected: TimelineById = { foo: { - id: 'foo', - savedObjectId: null, - columns: [], + ...basicTimeline, dataProviders: [ { - and: [], - id: '123', - name: '', // This value changed - enabled: true, - excluded: false, - kqlQuery: '', - type: DataProviderType.template, // value we are updating from default to template + ...basicDataProvider, + name: '', queryMatch: { field: '', - value: '{}', // This value changed + value: '{}', operator: IS_OPERATOR, }, + type: DataProviderType.template, }, ], - description: '', - deletedEventIds: [], - eventIdToNoteIds: {}, - excludedRowRendererIds: [], - highlightedDropAndProviderId: '', - historyIds: [], - isFavorite: false, - isLive: false, - isSelectAllChecked: false, - isLoading: false, - kqlMode: 'filter', - kqlQuery: { filterQuery: null, filterQueryDraft: null }, - loadingEventIds: [], - title: '', timelineType: TimelineType.template, - templateTimelineVersion: null, - templateTimelineId: null, - noteIds: [], - dateRange: { - start: '2020-07-07T08:20:18.966Z', - end: '2020-07-08T08:20:18.966Z', - }, - selectedEventIds: {}, - show: true, - showCheckboxes: false, - sort: { - columnId: '@timestamp', - sortDirection: Direction.desc, - }, - status: TimelineStatus.active, - pinnedEventIds: {}, - pinnedEventsSaveObject: {}, - itemsPerPage: 25, - itemsPerPageOptions: [10, 25, 50], - width: DEFAULT_TIMELINE_WIDTH, - isSaving: false, - version: null, }, }; + expect(update).toEqual(expected); }); + test('should update only one data provider and not two data providers AHH', () => { + const multiDataProvider = [ + ...timelineByIdTemplateMock.foo.dataProviders, + { + ...basicDataProvider, + id: '456', + type: DataProviderType.template, + }, + ]; - test('should update only one data provider and not two data providers', () => { - const multiDataProvider = timelineByIdTemplateMock.foo.dataProviders.concat({ - and: [], - id: '456', - name: 'data provider 1', - enabled: true, - excluded: false, - type: DataProviderType.template, - kqlQuery: '', - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, + const multiDataProviderMock = { + ...timelineByIdTemplateMock, + foo: { + ...timelineByIdTemplateMock.foo, + dataProviders: multiDataProvider, }, - }); - const multiDataProviderMock = set( - 'foo.dataProviders', - multiDataProvider, - timelineByIdTemplateMock - ); + }; const update = updateTimelineProviderType({ id: 'foo', providerId: '123', type: DataProviderType.template, // value we are updating from default to template timelineById: multiDataProviderMock, }); - const expected: TimelineById = { - foo: { - id: 'foo', - savedObjectId: null, - columns: [], - dataProviders: [ - { - and: [], - id: '123', - name: '', - enabled: true, - excluded: false, - type: DataProviderType.template, // value we are updating from default to template - kqlQuery: '', - queryMatch: { - field: '', - value: '{}', - operator: IS_OPERATOR, - }, - }, - { - and: [], - id: '456', - name: 'data provider 1', - enabled: true, - excluded: false, - kqlQuery: '', - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, - type: DataProviderType.template, - }, - ], - description: '', - deletedEventIds: [], - eventIdToNoteIds: {}, - excludedRowRendererIds: [], - highlightedDropAndProviderId: '', - historyIds: [], - isFavorite: false, - isLive: false, - isSelectAllChecked: false, - isLoading: false, - kqlMode: 'filter', - kqlQuery: { filterQuery: null, filterQueryDraft: null }, - loadingEventIds: [], - title: '', - timelineType: TimelineType.template, - templateTimelineId: null, - templateTimelineVersion: null, - noteIds: [], - dateRange: { - start: '2020-07-07T08:20:18.966Z', - end: '2020-07-08T08:20:18.966Z', - }, - selectedEventIds: {}, - show: true, - showCheckboxes: false, - sort: { - columnId: '@timestamp', - sortDirection: Direction.desc, + const expected = [ + { + ...basicDataProvider, + name: '', + type: DataProviderType.template, + queryMatch: { + field: '', + value: '{}', + operator: IS_OPERATOR, }, - status: TimelineStatus.active, - pinnedEventIds: {}, - pinnedEventsSaveObject: {}, - itemsPerPage: 25, - itemsPerPageOptions: [10, 25, 50], - width: DEFAULT_TIMELINE_WIDTH, - isSaving: false, - version: null, }, - }; - expect(update).toEqual(expected); + { + ...basicDataProvider, + id: '456', + type: DataProviderType.template, + }, + ]; + expect(update.foo.dataProviders).toEqual(expected); }); }); @@ -1770,32 +1276,15 @@ describe('Timeline', () => { let timelineByIdwithAndMock: TimelineById = timelineByIdMock; beforeEach(() => { const providerToAdd: DataProvider = { + ...basicDataProvider, and: [ { + ...basicDataProvider, id: '568', name: 'And Data Provider', - enabled: true, - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, - - excluded: false, - kqlQuery: '', }, ], id: '567', - name: 'data provider 1', - enabled: true, - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, - - excluded: false, - kqlQuery: '', }; timelineByIdwithAndMock = addTimelineProvider({ @@ -1824,7 +1313,7 @@ describe('Timeline', () => { timelineById: timelineByIdwithAndMock, andProviderId: '568', }); - expect(update.foo.dataProviders).not.toBe(timelineByIdMock.foo.dataProviders); + expect(update.foo.dataProviders).not.toBe(basicTimeline.dataProviders); }); test('should update the timeline and provider excluded from true to false', () => { @@ -1846,23 +1335,12 @@ describe('Timeline', () => { const multiAndDataProvider = timelineByIdwithAndMock.foo.dataProviders[ indexProvider ].and.concat({ + ...basicDataProvider, id: '456', name: 'new and data provider', - enabled: true, - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, - - excluded: false, - kqlQuery: '', }); - const multiAndDataProviderMock = set( - `foo.dataProviders[${indexProvider}].and`, - multiAndDataProvider, - timelineByIdwithAndMock - ); + const multiAndDataProviderMock = timelineByIdwithAndMock; + multiAndDataProviderMock.foo.dataProviders[indexProvider].and = multiAndDataProvider; const update = updateTimelineProviderExcluded({ id: 'foo', providerId: '567', @@ -1899,62 +1377,8 @@ describe('Timeline', () => { }); const expected: TimelineById = { foo: { - id: 'foo', - savedObjectId: null, - columns: [], - dataProviders: [ - { - and: [], - id: '123', - name: 'data provider 1', - enabled: true, - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, - - excluded: false, - kqlQuery: '', - }, - ], - description: '', - deletedEventIds: [], - eventIdToNoteIds: {}, - excludedRowRendererIds: [], - highlightedDropAndProviderId: '', - historyIds: [], - isFavorite: false, - isLive: false, - isSelectAllChecked: false, - isLoading: false, - kqlMode: 'filter', - kqlQuery: { filterQuery: null, filterQueryDraft: null }, - loadingEventIds: [], - title: '', - timelineType: TimelineType.default, - templateTimelineVersion: null, - templateTimelineId: null, - noteIds: [], - dateRange: { - start: '2020-07-07T08:20:18.966Z', - end: '2020-07-08T08:20:18.966Z', - }, - selectedEventIds: {}, - show: true, - showCheckboxes: false, - sort: { - columnId: '@timestamp', - sortDirection: Direction.desc, - }, - status: TimelineStatus.active, - pinnedEventIds: {}, - pinnedEventsSaveObject: {}, + ...basicTimeline, itemsPerPage: 50, - itemsPerPageOptions: [10, 25, 50], - width: DEFAULT_TIMELINE_WIDTH, - isSaving: false, - version: null, }, }; expect(update).toEqual(expected); @@ -1979,62 +1403,8 @@ describe('Timeline', () => { }); const expected: TimelineById = { foo: { - columns: [], - dataProviders: [ - { - and: [], - id: '123', - name: 'data provider 1', - enabled: true, - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, - - excluded: false, - kqlQuery: '', - }, - ], - description: '', - deletedEventIds: [], - eventIdToNoteIds: {}, - excludedRowRendererIds: [], - highlightedDropAndProviderId: '', - historyIds: [], - isFavorite: false, - isLive: false, - isSelectAllChecked: false, - isLoading: false, - id: 'foo', - savedObjectId: null, - kqlMode: 'filter', - kqlQuery: { filterQuery: null, filterQueryDraft: null }, - loadingEventIds: [], - title: '', - timelineType: TimelineType.default, - templateTimelineVersion: null, - templateTimelineId: null, - noteIds: [], - dateRange: { - start: '2020-07-07T08:20:18.966Z', - end: '2020-07-08T08:20:18.966Z', - }, - selectedEventIds: {}, - show: true, - showCheckboxes: false, - sort: { - columnId: '@timestamp', - sortDirection: Direction.desc, - }, - status: TimelineStatus.active, - pinnedEventIds: {}, - pinnedEventsSaveObject: {}, - itemsPerPage: 25, + ...basicTimeline, itemsPerPageOptions: [100, 200, 300], // updated - width: DEFAULT_TIMELINE_WIDTH, - isSaving: false, - version: null, }, }; expect(update).toEqual(expected); @@ -2057,25 +1427,22 @@ describe('Timeline', () => { providerId: '123', timelineById: timelineByIdMock, }); - expect(update).toEqual(set('foo.dataProviders', [], timelineByIdMock)); + expect(update.foo.dataProviders).toEqual([]); }); test('should remove only one data provider and not two data providers', () => { - const multiDataProvider = timelineByIdMock.foo.dataProviders.concat({ - and: [], + const multiDataProvider = basicTimeline.dataProviders.concat({ + ...basicDataProvider, id: '456', name: 'data provider 2', - enabled: true, - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, - - excluded: false, - kqlQuery: '', }); - const multiDataProviderMock = set('foo.dataProviders', multiDataProvider, timelineByIdMock); + const multiDataProviderMock = { + ...timelineByIdMock, + foo: { + ...timelineByIdMock.foo, + dataProviders: multiDataProvider, + }, + }; const update = removeTimelineProvider({ id: 'foo', providerId: '123', @@ -2083,62 +1450,14 @@ describe('Timeline', () => { }); const expected: TimelineById = { foo: { - columns: [], + ...basicTimeline, dataProviders: [ { - and: [], + ...basicDataProvider, id: '456', name: 'data provider 2', - enabled: true, - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, - - excluded: false, - kqlQuery: '', }, ], - description: '', - deletedEventIds: [], - eventIdToNoteIds: {}, - excludedRowRendererIds: [], - highlightedDropAndProviderId: '', - historyIds: [], - id: 'foo', - savedObjectId: null, - isFavorite: false, - isLive: false, - isSelectAllChecked: false, - isLoading: false, - kqlMode: 'filter', - kqlQuery: { filterQuery: null, filterQueryDraft: null }, - loadingEventIds: [], - title: '', - timelineType: TimelineType.default, - templateTimelineVersion: null, - templateTimelineId: null, - noteIds: [], - dateRange: { - start: '2020-07-07T08:20:18.966Z', - end: '2020-07-08T08:20:18.966Z', - }, - selectedEventIds: {}, - show: true, - showCheckboxes: false, - sort: { - columnId: '@timestamp', - sortDirection: Direction.desc, - }, - status: TimelineStatus.active, - pinnedEventIds: {}, - pinnedEventsSaveObject: {}, - itemsPerPage: 25, - itemsPerPageOptions: [10, 25, 50], - width: DEFAULT_TIMELINE_WIDTH, - isSaving: false, - version: null, }, }; expect(update).toEqual(expected); @@ -2147,99 +1466,58 @@ describe('Timeline', () => { test('should remove only first provider and not nested andProvider', () => { const dataProviders: DataProvider[] = [ { - and: [], + ...basicDataProvider, id: '111', - name: 'data provider 1', - enabled: true, - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, - - excluded: false, - kqlQuery: '', }, { - and: [], + ...basicDataProvider, id: '222', name: 'data provider 2', - enabled: true, - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, - - excluded: false, - kqlQuery: '', }, { - and: [], + ...basicDataProvider, id: '333', name: 'data provider 3', - enabled: true, - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, - - excluded: false, - kqlQuery: '', }, ]; - const multiDataProviderMock = set('foo.dataProviders', dataProviders, timelineByIdMock); - + const multiDataProviderMock = { + ...timelineByIdMock, + foo: { + ...timelineByIdMock.foo, + dataProviders, + }, + }; const andDataProvider: DataProvidersAnd = { + ...basicDataProvider, id: '211', name: 'And Data Provider', - enabled: true, - queryMatch: { - field: '', - value: '', - operator: IS_OPERATOR, - }, - - excluded: false, - kqlQuery: '', }; - const nestedMultiAndDataProviderMock = set( - 'foo.dataProviders[1].and', - [andDataProvider], - multiDataProviderMock - ); + const nestedMultiAndDataProviderMock = multiDataProviderMock; + multiDataProviderMock.foo.dataProviders[1].and = [andDataProvider]; const update = removeTimelineProvider({ id: 'foo', providerId: '222', timelineById: nestedMultiAndDataProviderMock, }); - expect(update).toEqual( - set( - 'foo.dataProviders', - [ - nestedMultiAndDataProviderMock.foo.dataProviders[0], - { ...andDataProvider, and: [] }, - nestedMultiAndDataProviderMock.foo.dataProviders[2], - ], - timelineByIdMock - ) - ); + expect(update.foo.dataProviders).toEqual([ + nestedMultiAndDataProviderMock.foo.dataProviders[0], + { ...andDataProvider, and: [] }, + nestedMultiAndDataProviderMock.foo.dataProviders[2], + ]); }); test('should remove only the first provider and keep multiple nested andProviders', () => { const multiDataProvider: DataProvider[] = [ { + ...basicDataProvider, and: [ { - enabled: true, + ...basicDataProvider, id: 'socket_closed-MSoH7GoB9v5HJNSHRYj1-user_name-root', name: 'root', - excluded: false, - kqlQuery: '', queryMatch: { field: 'user.name', value: 'root', @@ -2247,11 +1525,9 @@ describe('Timeline', () => { }, }, { - enabled: true, + ...basicDataProvider, id: 'executed-yioH7GoB9v5HJNSHKnp5-auditd_result-success', name: 'success', - excluded: false, - kqlQuery: '', queryMatch: { field: 'auditd.result', value: 'success', @@ -2259,11 +1535,8 @@ describe('Timeline', () => { }, }, ], - enabled: true, - excluded: false, id: 'hosts-table-hostName-suricata-iowa', name: 'suricata-iowa', - kqlQuery: '', queryMatch: { field: 'host.name', value: 'suricata-iowa', @@ -2272,7 +1545,13 @@ describe('Timeline', () => { }, ]; - const multiDataProviderMock = set('foo.dataProviders', multiDataProvider, timelineByIdMock); + const multiDataProviderMock = { + ...timelineByIdMock, + foo: { + ...timelineByIdMock.foo, + dataProviders: multiDataProvider, + }, + }; const update = removeTimelineProvider({ id: 'foo', @@ -2280,51 +1559,40 @@ describe('Timeline', () => { timelineById: multiDataProviderMock, }); - expect(update).toEqual( - set( - 'foo.dataProviders', - [ + expect(update.foo.dataProviders).toEqual([ + { + ...basicDataProvider, + id: 'socket_closed-MSoH7GoB9v5HJNSHRYj1-user_name-root', + name: 'root', + queryMatch: { + field: 'user.name', + value: 'root', + operator: ':', + }, + and: [ { - enabled: true, - id: 'socket_closed-MSoH7GoB9v5HJNSHRYj1-user_name-root', - name: 'root', - excluded: false, - kqlQuery: '', + ...basicDataProvider, + id: 'executed-yioH7GoB9v5HJNSHKnp5-auditd_result-success', + name: 'success', queryMatch: { - field: 'user.name', - value: 'root', + field: 'auditd.result', + value: 'success', operator: ':', }, - and: [ - { - enabled: true, - id: 'executed-yioH7GoB9v5HJNSHKnp5-auditd_result-success', - name: 'success', - excluded: false, - kqlQuery: '', - queryMatch: { - field: 'auditd.result', - value: 'success', - operator: ':', - }, - }, - ], }, ], - timelineByIdMock - ) - ); + }, + ]); }); test('should remove only the first AND provider when the first AND is deleted, and there are multiple andProviders', () => { const multiDataProvider: DataProvider[] = [ { + ...basicDataProvider, and: [ { - enabled: true, + ...basicDataProvider, id: 'socket_closed-MSoH7GoB9v5HJNSHRYj1-user_name-root', name: 'root', - excluded: false, - kqlQuery: '', queryMatch: { field: 'user.name', value: 'root', @@ -2332,11 +1600,9 @@ describe('Timeline', () => { }, }, { - enabled: true, + ...basicDataProvider, id: 'executed-yioH7GoB9v5HJNSHKnp5-auditd_result-success', name: 'success', - excluded: false, - kqlQuery: '', queryMatch: { field: 'auditd.result', value: 'success', @@ -2344,11 +1610,8 @@ describe('Timeline', () => { }, }, ], - enabled: true, - excluded: false, id: 'hosts-table-hostName-suricata-iowa', name: 'suricata-iowa', - kqlQuery: '', queryMatch: { field: 'host.name', value: 'suricata-iowa', @@ -2357,7 +1620,13 @@ describe('Timeline', () => { }, ]; - const multiDataProviderMock = set('foo.dataProviders', multiDataProvider, timelineByIdMock); + const multiDataProviderMock = { + ...timelineByIdMock, + foo: { + ...timelineByIdMock.foo, + dataProviders: multiDataProvider, + }, + }; const update = removeTimelineProvider({ andProviderId: 'socket_closed-MSoH7GoB9v5HJNSHRYj1-user_name-root', @@ -2366,52 +1635,41 @@ describe('Timeline', () => { timelineById: multiDataProviderMock, }); - expect(update).toEqual( - set( - 'foo.dataProviders', - [ + expect(update.foo.dataProviders).toEqual([ + { + ...basicDataProvider, + and: [ { - and: [ - { - enabled: true, - id: 'executed-yioH7GoB9v5HJNSHKnp5-auditd_result-success', - name: 'success', - excluded: false, - kqlQuery: '', - queryMatch: { - field: 'auditd.result', - value: 'success', - operator: ':', - }, - }, - ], - enabled: true, - excluded: false, - id: 'hosts-table-hostName-suricata-iowa', - name: 'suricata-iowa', - kqlQuery: '', + ...basicDataProvider, + id: 'executed-yioH7GoB9v5HJNSHKnp5-auditd_result-success', + name: 'success', queryMatch: { - field: 'host.name', - value: 'suricata-iowa', + field: 'auditd.result', + value: 'success', operator: ':', }, }, ], - timelineByIdMock - ) - ); + id: 'hosts-table-hostName-suricata-iowa', + name: 'suricata-iowa', + queryMatch: { + field: 'host.name', + value: 'suricata-iowa', + operator: ':', + }, + }, + ]); }); test('should remove only the second AND provider when the second AND is deleted, and there are multiple andProviders', () => { const multiDataProvider: DataProvider[] = [ { + ...basicDataProvider, and: [ { - enabled: true, + ...basicDataProvider, id: 'socket_closed-MSoH7GoB9v5HJNSHRYj1-user_name-root', name: 'root', - excluded: false, - kqlQuery: '', queryMatch: { field: 'user.name', value: 'root', @@ -2419,11 +1677,9 @@ describe('Timeline', () => { }, }, { - enabled: true, + ...basicDataProvider, id: 'executed-yioH7GoB9v5HJNSHKnp5-auditd_result-success', name: 'success', - excluded: false, - kqlQuery: '', queryMatch: { field: 'auditd.result', value: 'success', @@ -2431,11 +1687,8 @@ describe('Timeline', () => { }, }, ], - enabled: true, - excluded: false, id: 'hosts-table-hostName-suricata-iowa', name: 'suricata-iowa', - kqlQuery: '', queryMatch: { field: 'host.name', value: 'suricata-iowa', @@ -2444,7 +1697,13 @@ describe('Timeline', () => { }, ]; - const multiDataProviderMock = set('foo.dataProviders', multiDataProvider, timelineByIdMock); + const multiDataProviderMock = { + ...timelineByIdMock, + foo: { + ...timelineByIdMock.foo, + dataProviders: multiDataProvider, + }, + }; const update = removeTimelineProvider({ andProviderId: 'executed-yioH7GoB9v5HJNSHKnp5-auditd_result-success', @@ -2453,40 +1712,30 @@ describe('Timeline', () => { timelineById: multiDataProviderMock, }); - expect(update).toEqual( - set( - 'foo.dataProviders', - [ + expect(update.foo.dataProviders).toEqual([ + { + ...basicDataProvider, + and: [ { - and: [ - { - enabled: true, - id: 'socket_closed-MSoH7GoB9v5HJNSHRYj1-user_name-root', - name: 'root', - excluded: false, - kqlQuery: '', - queryMatch: { - field: 'user.name', - value: 'root', - operator: ':', - }, - }, - ], - enabled: true, - excluded: false, - id: 'hosts-table-hostName-suricata-iowa', - name: 'suricata-iowa', - kqlQuery: '', + ...basicDataProvider, + id: 'socket_closed-MSoH7GoB9v5HJNSHRYj1-user_name-root', + name: 'root', queryMatch: { - field: 'host.name', - value: 'suricata-iowa', + field: 'user.name', + value: 'root', operator: ':', }, }, ], - timelineByIdMock - ) - ); + id: 'hosts-table-hostName-suricata-iowa', + name: 'suricata-iowa', + queryMatch: { + field: 'host.name', + value: 'suricata-iowa', + operator: ':', + }, + }, + ]); }); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts index d15bce5e217fa..1d956e02e7083 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts @@ -44,6 +44,7 @@ import { updateDescription, updateEventType, updateHighlightedDropAndProviderId, + updateIndexNames, updateIsFavorite, updateIsLive, updateIsLoading, @@ -135,6 +136,7 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState) show, columns, itemsPerPage, + indexNames, kqlQuery, sort, showCheckboxes, @@ -152,6 +154,7 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState) filters, id, itemsPerPage, + indexNames, kqlQuery, sort, show, @@ -521,4 +524,14 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState) ...state, insertTimeline, })) + .case(updateIndexNames, (state, { id, indexNames }) => ({ + ...state, + timelineById: { + ...state.timelineById, + [id]: { + ...state.timelineById[id], + indexNames, + }, + }, + })) .build(); diff --git a/x-pack/plugins/security_solution/scripts/beat_docs/build.js b/x-pack/plugins/security_solution/scripts/beat_docs/build.js new file mode 100644 index 0000000000000..9b3607593a5db --- /dev/null +++ b/x-pack/plugins/security_solution/scripts/beat_docs/build.js @@ -0,0 +1,233 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +require('../../../../../src/setup_node_env'); + +// eslint-disable-next-line import/no-extraneous-dependencies +const extract = require('extract-zip'); +const fs = require('fs'); +// eslint-disable-next-line import/no-extraneous-dependencies +const yaml = require('js-yaml'); +const https = require('https'); +// eslint-disable-next-line import/no-extraneous-dependencies +const { get, isArray, isEmpty, isNumber, isString, pick } = require('lodash'); +// eslint-disable-next-line import/no-extraneous-dependencies +const Q = require('q'); +// eslint-disable-next-line import/no-extraneous-dependencies +const rimraf = require('rimraf'); +const { resolve } = require('path'); +// eslint-disable-next-line import/no-extraneous-dependencies +const tar = require('tar'); +const zlib = require('zlib'); + +const OUTPUT_DIRECTORY = resolve('scripts', 'beat_docs'); +const OUTPUT_SERVER_DIRECTORY = resolve('server', 'utils', 'beat_schema'); + +const beats = [ + { + filePath: `${OUTPUT_DIRECTORY}/auditbeat-7.9.0-darwin-x86_64.tar.gz`, + index: 'auditbeat-*', + outputDir: `${OUTPUT_DIRECTORY}/auditbeat-7.9.0-darwin-x86_64`, + url: + 'https://artifacts.elastic.co/downloads/beats/auditbeat/auditbeat-7.9.0-darwin-x86_64.tar.gz', + }, + { + filePath: `${OUTPUT_DIRECTORY}/filebeat-7.9.0-darwin-x86_64.tar.gz`, + index: 'filebeat-*', + outputDir: `${OUTPUT_DIRECTORY}/filebeat-7.9.0-darwin-x86_64`, + url: + 'https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.9.0-darwin-x86_64.tar.gz', + }, + { + filePath: `${OUTPUT_DIRECTORY}/packetbeat-7.9.0-darwin-x86_64.tar.gz`, + index: 'packetbeat-*', + outputDir: `${OUTPUT_DIRECTORY}/packetbeat-7.9.0-darwin-x86_64`, + url: + 'https://artifacts.elastic.co/downloads/beats/packetbeat/packetbeat-7.9.0-darwin-x86_64.tar.gz', + }, + { + filePath: `${OUTPUT_DIRECTORY}/winlogbeat-7.9.0-windows-x86_64.zip`, + index: 'winlogbeat-*', + outputDir: `${OUTPUT_DIRECTORY}`, + url: + 'https://artifacts.elastic.co/downloads/beats/winlogbeat/winlogbeat-7.9.0-windows-x86_64.zip', + }, +]; + +const download = async (url, filepath) => { + const fileStream = fs.createWriteStream(filepath); + const deferred = Q.defer(); + + fileStream + .on('open', function () { + https.get(url, function (res) { + res.on('error', function (err) { + deferred.reject(err); + }); + + res.pipe(fileStream); + }); + }) + .on('error', function (err) { + deferred.reject(err); + }) + .on('finish', function () { + deferred.resolve(filepath); + }); + + return deferred.promise; +}; + +const paramsToPick = ['category', 'description', 'example', 'name', 'type', 'format']; + +const onlyStringOrNumber = (fields) => + Object.keys(fields).reduce((acc, item) => { + let value = get(fields, item); + if (item === 'description' && isString(value)) { + value = value.replace(/\n/g, ' '); + } + return { + ...acc, + [item]: isString(value) || isNumber(value) ? value : JSON.stringify(value), + }; + }, {}); + +const convertFieldsToHash = (schemaFields, beatFields, path) => + schemaFields.fields && isArray(schemaFields.fields) + ? schemaFields.fields.reduce((accumulator, item) => { + if (item.name) { + const attr = isEmpty(path) ? item.name : `${path}.${item.name}`; + const splitAttr = attr.split('.'); + const category = splitAttr.length === 1 ? 'base' : splitAttr[0]; + const myItem = { + ...item, + category, + name: attr, + }; + if (!isEmpty(item.fields)) { + return { + ...accumulator, + ...convertFieldsToHash(myItem, beatFields, attr), + }; + } else if (beatFields[attr] === undefined) { + return { + ...accumulator, + [attr]: onlyStringOrNumber(pick(myItem, paramsToPick)), + }; + } + } + return accumulator; + }, {}) + : {}; + +const convertSchemaToHash = (schema, beatFields) => { + return schema.reduce((accumulator, item) => { + if (item.fields != null && !isEmpty(item.fields)) { + return { + ...accumulator, + ...convertFieldsToHash(item, accumulator), + }; + } + return accumulator; + }, beatFields); +}; + +const manageZipFields = async (beat, filePath, beatFields) => + new Promise((resolve, reject) => { + extract(filePath, { dir: beat.outputDir }, (err) => { + if (err) { + return reject(new Error(err)); + } + console.log('building fields', beat.index); + const obj = yaml.load( + fs.readFileSync(`${beat.outputDir}/winlogbeat-7.9.0-windows-x86_64/fields.yml`, { + encoding: 'utf-8', + }) + ); + const eBeatFields = convertSchemaToHash(obj, beatFields); + console.log('deleting files', beat.index); + rimraf.sync(`${beat.outputDir}/winlogbeat-7.9.0-windows-x86_64`); + rimraf.sync(beat.filePath); + resolve(eBeatFields); + }); + }); + +const manageTarFields = async (beat, filePath, beatFields) => + new Promise((resolve, reject) => { + fs.createReadStream(filePath) + .pipe(zlib.createGunzip()) + .pipe( + tar.extract({ + sync: true, + cwd: OUTPUT_DIRECTORY, + filter: function (path) { + return path.includes('fields.yml'); + }, + }) + ) + .on('end', function (err) { + if (err) { + return reject(new Error(err)); + } + console.log('building fields', beat.index); + const obj = yaml.load( + fs.readFileSync(`${beat.outputDir}/fields.yml`, { encoding: 'utf-8' }) + ); + const ebeatFields = convertSchemaToHash(obj, beatFields); + console.log('deleting files', beat.index); + rimraf.sync(beat.outputDir); + rimraf.sync(beat.filePath); + resolve(ebeatFields); + }); + }); + +async function main() { + let beatFields = { + _id: { + category: 'base', + description: 'Each document has an _id that uniquely identifies it', + example: 'Y-6TfmcB0WOhS6qyMv3s', + name: '_id', + type: 'keyword', + }, + _index: { + category: 'base', + description: + 'An index is like a ‘database’ in a relational database. It has a mapping which defines multiple types. An index is a logical namespace which maps to one or more primary shards and can have zero or more replica shards.', + example: 'auditbeat-8.0.0-2019.02.19-000001', + name: '_index', + type: 'keyword', + }, + }; + + for (const myBeat of beats) { + console.log('downloading', myBeat.index); + const filepath = await download(myBeat.url, myBeat.filePath); + if (myBeat.index === 'winlogbeat-*') { + beatFields = await manageZipFields(myBeat, filepath, beatFields); + } else { + beatFields = await manageTarFields(myBeat, filepath, beatFields); + } + console.log('done for', myBeat.index); + } + const body = `/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + + import { BeatFields } from '../../../common/search_strategy/security_solution/beat_fields'; + + /* eslint-disable @typescript-eslint/naming-convention */ + export const fieldsBeat: BeatFields = + ${JSON.stringify(beatFields, null, 2)}; + `; + fs.writeFileSync(`${OUTPUT_SERVER_DIRECTORY}/fields.ts`, body, 'utf-8'); +} + +if (require.main === module) { + main(); +} diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/ancestry_query_handler.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/ancestry_query_handler.ts index b796913118c99..5bc911fb075b5 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/ancestry_query_handler.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/ancestry_query_handler.ts @@ -9,7 +9,7 @@ import { ILegacyScopedClusterClient } from 'kibana/server'; import { parentEntityIDSafeVersion, entityIDSafeVersion, - getAncestryAsArray, + ancestry, } from '../../../../../common/endpoint/models/event'; import { SafeResolverAncestry, @@ -35,7 +35,8 @@ export class AncestryQueryHandler implements QueryHandler legacyEndpointID: string | undefined, originNode: SafeResolverLifecycleNode | undefined ) { - this.ancestorsToFind = getAncestryAsArray(originNode?.lifecycle[0]).slice(0, levels); + const event = originNode?.lifecycle[0]; + this.ancestorsToFind = (event ? ancestry(event) : []).slice(0, levels); this.query = new LifecycleQuery(indexPattern, legacyEndpointID); // add the origin node to the response if it exists @@ -108,7 +109,7 @@ export class AncestryQueryHandler implements QueryHandler this.levels = this.levels - ancestryNodes.size; // the results come back in ascending order on timestamp so the first entry in the // results should be the further ancestor (most distant grandparent) - this.ancestorsToFind = getAncestryAsArray(results[0]).slice(0, this.levels); + this.ancestorsToFind = ancestry(results[0]).slice(0, this.levels); }; /** diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/children_helper.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/children_helper.ts index f54472141c1de..1a871891b1ed5 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/children_helper.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/children_helper.ts @@ -4,12 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - parentEntityIDSafeVersion, - isProcessRunning, - getAncestryAsArray, - entityIDSafeVersion, -} from '../../../../../common/endpoint/models/event'; +import * as eventModel from '../../../../../common/endpoint/models/event'; import { SafeResolverChildren, SafeResolverChildNode, @@ -72,7 +67,7 @@ export class ChildrenNodesHelper { */ addLifecycleEvents(lifecycle: SafeResolverEvent[]) { for (const event of lifecycle) { - const entityID = entityIDSafeVersion(event); + const entityID = eventModel.entityIDSafeVersion(event); if (entityID) { const cachedChild = this.getOrCreateChildNode(entityID); cachedChild.lifecycle.push(event); @@ -93,19 +88,19 @@ export class ChildrenNodesHelper { const nonLeafNodes: Set = new Set(); const isDistantGrandchild = (event: ChildEvent) => { - const ancestry = getAncestryAsArray(event); + const ancestry = eventModel.ancestry(event); return ancestry.length > 0 && queriedNodes.has(ancestry[ancestry.length - 1]); }; for (const event of startEvents) { - const parentID = parentEntityIDSafeVersion(event); - const entityID = entityIDSafeVersion(event); - if (parentID && entityID && isProcessRunning(event)) { + const parentID = eventModel.parentEntityIDSafeVersion(event); + const entityID = eventModel.entityIDSafeVersion(event); + if (parentID && entityID && eventModel.isProcessRunning(event)) { // don't actually add the start event to the node, because that'll be done in // a different call const childNode = this.getOrCreateChildNode(entityID); - const ancestry = getAncestryAsArray(event); + const ancestry = eventModel.ancestry(event); // This is to handle the following unlikely but possible scenario: // if an alert was generated by the kernel process (parent process of all other processes) then // the direct children of that process would only have an ancestry array of [parent_kernel], a single value in the array. diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts index ec4d1efb81b11..7cf3d467c10c2 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts @@ -6,6 +6,7 @@ import { RequestHandler, RequestHandlerContext } from 'kibana/server'; import { + DeleteTrustedAppsRequestParams, GetTrustedAppsListRequest, GetTrustedListAppsResponse, PostTrustedAppCreateRequest, @@ -13,7 +14,6 @@ import { import { EndpointAppContext } from '../../types'; import { exceptionItemToTrustedAppItem, newTrustedAppItemToExceptionItem } from './utils'; import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../lists/common/constants'; -import { DeleteTrustedAppsRequestParams } from './types'; import { ExceptionListClient } from '../../../../../lists/server'; const exceptionListClientFromContext = (context: RequestHandlerContext): ExceptionListClient => { diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/trusted_apps.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/trusted_apps.test.ts index 35d0bf1116148..98c9b79f32d6b 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/trusted_apps.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/trusted_apps.test.ts @@ -18,6 +18,7 @@ import { TRUSTED_APPS_LIST_API, } from '../../../../common/endpoint/constants'; import { + DeleteTrustedAppsRequestParams, GetTrustedAppsListRequest, PostTrustedAppCreateRequest, } from '../../../../common/endpoint/types'; @@ -26,8 +27,10 @@ import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../lists/common/const import { EndpointAppContext } from '../../types'; import { ExceptionListClient, ListClient } from '../../../../../lists/server'; import { listMock } from '../../../../../lists/server/mocks'; -import { ExceptionListItemSchema } from '../../../../../lists/common/schemas/response'; -import { DeleteTrustedAppsRequestParams } from './types'; +import { + ExceptionListItemSchema, + FoundExceptionListItemSchema, +} from '../../../../../lists/common/schemas/response'; import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; type RequestHandlerContextWithLists = ReturnType & { @@ -125,6 +128,97 @@ describe('when invoking endpoint trusted apps route handlers', () => { }); }); + it('should map Exception List Item to Trusted App item', async () => { + const request = createListRequest(10, 100); + const emptyResponse: FoundExceptionListItemSchema = { + data: [ + { + _tags: ['os:windows'], + _version: undefined, + comments: [], + created_at: '2020-09-21T19:43:48.240Z', + created_by: 'test', + description: '', + entries: [ + { + field: 'process.hash.sha256', + operator: 'included', + type: 'match', + value: 'a4370c0cf81686c0b696fa6261c9d3e0d810ae704ab8301839dffd5d5112f476', + }, + { + field: 'process.hash.sha1', + operator: 'included', + type: 'match', + value: 'aedb279e378bed6c2db3c9dc9e12ba635e0b391c', + }, + { + field: 'process.hash.md5', + operator: 'included', + type: 'match', + value: '741462ab431a22233c787baab9b653c7', + }, + ], + id: '1', + item_id: '11', + list_id: 'trusted apps test', + meta: undefined, + name: 'test', + namespace_type: 'agnostic', + tags: [], + tie_breaker_id: '1', + type: 'simple', + updated_at: '2020-09-21T19:43:48.240Z', + updated_by: 'test', + }, + ], + page: 10, + per_page: 100, + total: 0, + }; + + exceptionsListClient.findExceptionListItem.mockResolvedValue(emptyResponse); + await routeHandler(context, request, response); + + expect(response.ok).toHaveBeenCalledWith({ + body: { + data: [ + { + created_at: '2020-09-21T19:43:48.240Z', + created_by: 'test', + description: '', + entries: [ + { + field: 'process.hash.*', + operator: 'included', + type: 'match', + value: 'a4370c0cf81686c0b696fa6261c9d3e0d810ae704ab8301839dffd5d5112f476', + }, + { + field: 'process.hash.*', + operator: 'included', + type: 'match', + value: 'aedb279e378bed6c2db3c9dc9e12ba635e0b391c', + }, + { + field: 'process.hash.*', + operator: 'included', + type: 'match', + value: '741462ab431a22233c787baab9b653c7', + }, + ], + id: '1', + name: 'test', + os: 'windows', + }, + ], + page: 10, + per_page: 100, + total: 0, + }, + }); + }); + it('should log unexpected error if one occurs', async () => { exceptionsListClient.findExceptionListItem.mockImplementation(() => { throw new Error('expected error'); @@ -138,24 +232,26 @@ describe('when invoking endpoint trusted apps route handlers', () => { describe('when creating a trusted app', () => { let routeHandler: RequestHandler; - const createNewTrustedAppBody = (): PostTrustedAppCreateRequest => ({ + const createNewTrustedAppBody = (): { + -readonly [k in keyof PostTrustedAppCreateRequest]: PostTrustedAppCreateRequest[k]; + } => ({ name: 'Some Anti-Virus App', description: 'this one is ok', os: 'windows', entries: [ { - field: 'process.path', + field: 'process.path.text', type: 'match', operator: 'included', value: 'c:/programs files/Anti-Virus', }, ], }); - const createPostRequest = () => { + const createPostRequest = (body?: PostTrustedAppCreateRequest) => { return httpServerMock.createKibanaRequest({ path: TRUSTED_APPS_LIST_API, method: 'post', - body: createNewTrustedAppBody(), + body: body ?? createNewTrustedAppBody(), }); }; @@ -197,7 +293,7 @@ describe('when invoking endpoint trusted apps route handlers', () => { description: 'this one is ok', entries: [ { - field: 'process.path', + field: 'process.path.text', operator: 'included', type: 'match', value: 'c:/programs files/Anti-Virus', @@ -224,7 +320,7 @@ describe('when invoking endpoint trusted apps route handlers', () => { description: 'this one is ok', entries: [ { - field: 'process.path', + field: 'process.path.text', operator: 'included', type: 'match', value: 'c:/programs files/Anti-Virus', @@ -247,6 +343,134 @@ describe('when invoking endpoint trusted apps route handlers', () => { expect(response.internalError).toHaveBeenCalled(); expect(endpointAppContext.logFactory.get('trusted_apps').error).toHaveBeenCalled(); }); + + it('should trim trusted app entry name', async () => { + const newTrustedApp = createNewTrustedAppBody(); + newTrustedApp.name = `\n ${newTrustedApp.name} \r\n`; + const request = createPostRequest(newTrustedApp); + await routeHandler(context, request, response); + expect(exceptionsListClient.createExceptionListItem.mock.calls[0][0].name).toEqual( + 'Some Anti-Virus App' + ); + }); + + it('should trim condition entry values', async () => { + const newTrustedApp = createNewTrustedAppBody(); + newTrustedApp.entries.push({ + field: 'process.path.text', + value: '\n some value \r\n ', + operator: 'included', + type: 'match', + }); + const request = createPostRequest(newTrustedApp); + await routeHandler(context, request, response); + expect(exceptionsListClient.createExceptionListItem.mock.calls[0][0].entries).toEqual([ + { + field: 'process.path.text', + operator: 'included', + type: 'match', + value: 'c:/programs files/Anti-Virus', + }, + { + field: 'process.path.text', + value: 'some value', + operator: 'included', + type: 'match', + }, + ]); + }); + + it('should convert hash values to lowercase', async () => { + const newTrustedApp = createNewTrustedAppBody(); + newTrustedApp.entries.push({ + field: 'process.hash.*', + value: '741462AB431A22233C787BAAB9B653C7', + operator: 'included', + type: 'match', + }); + const request = createPostRequest(newTrustedApp); + await routeHandler(context, request, response); + expect(exceptionsListClient.createExceptionListItem.mock.calls[0][0].entries).toEqual([ + { + field: 'process.path.text', + operator: 'included', + type: 'match', + value: 'c:/programs files/Anti-Virus', + }, + { + field: 'process.hash.md5', + value: '741462ab431a22233c787baab9b653c7', + operator: 'included', + type: 'match', + }, + ]); + }); + + it('should detect md5 hash', async () => { + const newTrustedApp = createNewTrustedAppBody(); + newTrustedApp.entries = [ + { + field: 'process.hash.*', + value: '741462ab431a22233c787baab9b653c7', + operator: 'included', + type: 'match', + }, + ]; + const request = createPostRequest(newTrustedApp); + await routeHandler(context, request, response); + expect(exceptionsListClient.createExceptionListItem.mock.calls[0][0].entries).toEqual([ + { + field: 'process.hash.md5', + value: '741462ab431a22233c787baab9b653c7', + operator: 'included', + type: 'match', + }, + ]); + }); + + it('should detect sha1 hash', async () => { + const newTrustedApp = createNewTrustedAppBody(); + newTrustedApp.entries = [ + { + field: 'process.hash.*', + value: 'aedb279e378bed6c2db3c9dc9e12ba635e0b391c', + operator: 'included', + type: 'match', + }, + ]; + const request = createPostRequest(newTrustedApp); + await routeHandler(context, request, response); + expect(exceptionsListClient.createExceptionListItem.mock.calls[0][0].entries).toEqual([ + { + field: 'process.hash.sha1', + value: 'aedb279e378bed6c2db3c9dc9e12ba635e0b391c', + operator: 'included', + type: 'match', + }, + ]); + }); + + it('should detect sha256 hash', async () => { + const newTrustedApp = createNewTrustedAppBody(); + newTrustedApp.entries = [ + { + field: 'process.hash.*', + value: 'a4370c0cf81686c0b696fa6261c9d3e0d810ae704ab8301839dffd5d5112f476', + operator: 'included', + type: 'match', + }, + ]; + const request = createPostRequest(newTrustedApp); + await routeHandler(context, request, response); + expect(exceptionsListClient.createExceptionListItem.mock.calls[0][0].entries).toEqual([ + { + field: 'process.hash.sha256', + value: 'a4370c0cf81686c0b696fa6261c9d3e0d810ae704ab8301839dffd5d5112f476', + operator: 'included', + type: 'match', + }, + ]); + }); }); describe('when deleting a trusted app', () => { diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/types.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/types.ts deleted file mode 100644 index 13c8bcfc20793..0000000000000 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/types.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { TypeOf } from '@kbn/config-schema'; -import { DeleteTrustedAppsRequestSchema } from '../../../../common/endpoint/schema/trusted_apps'; - -export type DeleteTrustedAppsRequestParams = TypeOf; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/utils.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/utils.ts index 794c1db4b49aa..2b8129ab950c6 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/utils.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/utils.ts @@ -10,7 +10,7 @@ import { NewTrustedApp, TrustedApp } from '../../../../common/endpoint/types'; import { ExceptionListClient } from '../../../../../lists/server'; import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../lists/common/constants'; -type NewExecptionItem = Parameters[0]; +type NewExceptionItem = Parameters[0]; /** * Map an ExcptionListItem to a TrustedApp item @@ -23,7 +23,15 @@ export const exceptionItemToTrustedAppItem = ( const { entries, description, created_by, created_at, name, _tags, id } = exceptionListItem; const os = osFromTagsList(_tags); return { - entries, + entries: entries.map((entry) => { + if (entry.field.startsWith('process.hash')) { + return { + ...entry, + field: 'process.hash.*', + }; + } + return entry; + }), description, created_at, created_by, @@ -51,22 +59,46 @@ export const newTrustedAppItemToExceptionItem = ({ entries, name, description = '', -}: NewTrustedApp): NewExecptionItem => { +}: NewTrustedApp): NewExceptionItem => { return { _tags: tagsListFromOs(os), comments: [], description, - entries, + // @ts-ignore + entries: entries.map(({ value, ...newEntry }) => { + let newValue = value.trim(); + + if (newEntry.field === 'process.hash.*') { + newValue = newValue.toLowerCase(); + newEntry.field = `process.hash.${hashType(newValue)}`; + } + + return { + ...newEntry, + value: newValue, + }; + }), itemId: uuid.v4(), listId: ENDPOINT_TRUSTED_APPS_LIST_ID, meta: undefined, - name, + name: name.trim(), namespaceType: 'agnostic', tags: [], type: 'simple', }; }; -const tagsListFromOs = (os: NewTrustedApp['os']): NewExecptionItem['_tags'] => { +const tagsListFromOs = (os: NewTrustedApp['os']): NewExceptionItem['_tags'] => { return [`os:${os}`]; }; + +const hashType = (hash: string): 'md5' | 'sha256' | 'sha1' | undefined => { + switch (hash.length) { + case 32: + return 'md5'; + case 40: + return 'sha1'; + case 64: + return 'sha256'; + } +}; diff --git a/x-pack/plugins/security_solution/server/graphql/index.ts b/x-pack/plugins/security_solution/server/graphql/index.ts index 7e25735707893..2de6ef32b5703 100644 --- a/x-pack/plugins/security_solution/server/graphql/index.ts +++ b/x-pack/plugins/security_solution/server/graphql/index.ts @@ -15,7 +15,6 @@ import { ipDetailsSchemas } from './ip_details'; import { kpiHostsSchema } from './kpi_hosts'; import { kpiNetworkSchema } from './kpi_network'; import { networkSchema } from './network'; -import { overviewSchema } from './overview'; import { dateSchema } from './scalar_date'; import { noteSchema } from './note'; import { pinnedEventSchema } from './pinned_event'; @@ -26,8 +25,6 @@ import { toNumberSchema } from './scalar_to_number_array'; import { sourceStatusSchema } from './source_status'; import { sourcesSchema } from './sources'; import { timelineSchema } from './timeline'; -import { tlsSchema } from './tls'; -import { uncommonProcessesSchema } from './uncommon_processes'; import { whoAmISchema } from './who_am_i'; import { matrixHistogramSchema } from './matrix_histogram'; export const schemas = [ @@ -46,14 +43,11 @@ export const schemas = [ matrixHistogramSchema, networkSchema, noteSchema, - overviewSchema, pinnedEventSchema, rootSchema, sourcesSchema, sourceStatusSchema, sharedSchema, timelineSchema, - tlsSchema, - uncommonProcessesSchema, whoAmISchema, ]; diff --git a/x-pack/plugins/security_solution/server/graphql/overview/resolvers.ts b/x-pack/plugins/security_solution/server/graphql/overview/resolvers.ts deleted file mode 100644 index a7bafabb64092..0000000000000 --- a/x-pack/plugins/security_solution/server/graphql/overview/resolvers.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { SourceResolvers } from '../../graphql/types'; -import { AppResolverOf, ChildResolverOf } from '../../lib/framework'; -import { Overview } from '../../lib/overview'; -import { createOptions } from '../../utils/build_query/create_options'; -import { QuerySourceResolver } from '../sources/resolvers'; - -export type QueryOverviewNetworkResolver = ChildResolverOf< - AppResolverOf, - QuerySourceResolver ->; - -export type QueryOverviewHostResolver = ChildResolverOf< - AppResolverOf, - QuerySourceResolver ->; - -export interface OverviewResolversDeps { - overview: Overview; -} - -export const createOverviewResolvers = ( - libs: OverviewResolversDeps -): { - Source: { - OverviewHost: QueryOverviewHostResolver; - OverviewNetwork: QueryOverviewNetworkResolver; - }; -} => ({ - Source: { - async OverviewNetwork(source, args, { req }, info) { - const options = { ...createOptions(source, args, info) }; - return libs.overview.getOverviewNetwork(req, options); - }, - async OverviewHost(source, args, { req }, info) { - const options = { ...createOptions(source, args, info) }; - return libs.overview.getOverviewHost(req, options); - }, - }, -}); diff --git a/x-pack/plugins/security_solution/server/graphql/overview/schema.gql.ts b/x-pack/plugins/security_solution/server/graphql/overview/schema.gql.ts deleted file mode 100644 index 7ab4f9fdb18d6..0000000000000 --- a/x-pack/plugins/security_solution/server/graphql/overview/schema.gql.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import gql from 'graphql-tag'; - -export const overviewSchema = gql` - type OverviewNetworkData { - auditbeatSocket: Float - filebeatCisco: Float - filebeatNetflow: Float - filebeatPanw: Float - filebeatSuricata: Float - filebeatZeek: Float - packetbeatDNS: Float - packetbeatFlow: Float - packetbeatTLS: Float - inspect: Inspect - } - - type OverviewHostData { - auditbeatAuditd: Float - auditbeatFIM: Float - auditbeatLogin: Float - auditbeatPackage: Float - auditbeatProcess: Float - auditbeatUser: Float - endgameDns: Float - endgameFile: Float - endgameImageLoad: Float - endgameNetwork: Float - endgameProcess: Float - endgameRegistry: Float - endgameSecurity: Float - filebeatSystemModule: Float - winlogbeatSecurity: Float - winlogbeatMWSysmonOperational: Float - inspect: Inspect - } - - extend type Source { - OverviewNetwork( - id: String - timerange: TimerangeInput! - filterQuery: String - defaultIndex: [String!]! - ): OverviewNetworkData - OverviewHost( - id: String - timerange: TimerangeInput! - filterQuery: String - defaultIndex: [String!]! - ): OverviewHostData - } -`; diff --git a/x-pack/plugins/security_solution/server/graphql/source_status/schema.gql.ts b/x-pack/plugins/security_solution/server/graphql/source_status/schema.gql.ts index 3062113f1b635..60dc563a3e8d2 100644 --- a/x-pack/plugins/security_solution/server/graphql/source_status/schema.gql.ts +++ b/x-pack/plugins/security_solution/server/graphql/source_status/schema.gql.ts @@ -37,6 +37,6 @@ export const sourceStatusSchema = gql` "Whether the configured alias or wildcard pattern resolve to any auditbeat indices" indicesExist(defaultIndex: [String!]!): Boolean! "The list of fields defined in the index mappings" - indexFields(defaultIndex: [String!]!): [IndexField!]! + indexFields(defaultIndex: [String!]!): [String!]! } `; diff --git a/x-pack/plugins/security_solution/server/graphql/timeline/schema.gql.ts b/x-pack/plugins/security_solution/server/graphql/timeline/schema.gql.ts index 573539e1bb54f..70596a1b41ea0 100644 --- a/x-pack/plugins/security_solution/server/graphql/timeline/schema.gql.ts +++ b/x-pack/plugins/security_solution/server/graphql/timeline/schema.gql.ts @@ -167,6 +167,7 @@ export const timelineSchema = gql` filters: [FilterTimelineInput!] kqlMode: String kqlQuery: SerializedFilterQueryInput + indexNames: [String!] title: String templateTimelineId: String templateTimelineVersion: Int @@ -269,6 +270,7 @@ export const timelineSchema = gql` filters: [FilterTimelineResult!] kqlMode: String kqlQuery: SerializedFilterQueryResult + indexNames: [String!] notes: [NoteResult!] noteIds: [String!] pinnedEventIds: [String!] diff --git a/x-pack/plugins/security_solution/server/graphql/tls/resolvers.ts b/x-pack/plugins/security_solution/server/graphql/tls/resolvers.ts deleted file mode 100644 index bfa3fddc3c8a5..0000000000000 --- a/x-pack/plugins/security_solution/server/graphql/tls/resolvers.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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { SourceResolvers } from '../../graphql/types'; -import { AppResolverOf, ChildResolverOf } from '../../lib/framework'; -import { TLS, TlsRequestOptions } from '../../lib/tls'; -import { createOptionsPaginated } from '../../utils/build_query/create_options'; -import { QuerySourceResolver } from '../sources/resolvers'; - -export type QueryTlsResolver = ChildResolverOf< - AppResolverOf, - QuerySourceResolver ->; - -export interface TlsResolversDeps { - tls: TLS; -} - -export const createTlsResolvers = ( - libs: TlsResolversDeps -): { - Source: { - Tls: QueryTlsResolver; - }; -} => ({ - Source: { - async Tls(source, args, { req }, info) { - const options: TlsRequestOptions = { - ...createOptionsPaginated(source, args, info), - ip: args.ip, - sort: args.sort, - flowTarget: args.flowTarget, - }; - return libs.tls.getTls(req, options); - }, - }, -}); diff --git a/x-pack/plugins/security_solution/server/graphql/tls/schema.gql.ts b/x-pack/plugins/security_solution/server/graphql/tls/schema.gql.ts deleted file mode 100644 index 452c615c65aa5..0000000000000 --- a/x-pack/plugins/security_solution/server/graphql/tls/schema.gql.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import gql from 'graphql-tag'; - -export const tlsSchema = gql` - enum TlsFields { - _id - } - type TlsNode { - _id: String - timestamp: Date - notAfter: [String!] - subjects: [String!] - ja3: [String!] - issuers: [String!] - } - input TlsSortField { - field: TlsFields! - direction: Direction! - } - type TlsEdges { - node: TlsNode! - cursor: CursorType! - } - type TlsData { - edges: [TlsEdges!]! - totalCount: Float! - pageInfo: PageInfoPaginated! - inspect: Inspect - } - extend type Source { - Tls( - filterQuery: String - id: String - ip: String! - pagination: PaginationInputPaginated! - sort: TlsSortField! - flowTarget: FlowTargetSourceDest! - timerange: TimerangeInput! - defaultIndex: [String!]! - ): TlsData! - } -`; diff --git a/x-pack/plugins/security_solution/server/graphql/types.ts b/x-pack/plugins/security_solution/server/graphql/types.ts index 7638ebd03f6b1..d10dfb16a9b8a 100644 --- a/x-pack/plugins/security_solution/server/graphql/types.ts +++ b/x-pack/plugins/security_solution/server/graphql/types.ts @@ -97,12 +97,6 @@ export interface NetworkHttpSortField { direction: Direction; } -export interface TlsSortField { - field: TlsFields; - - direction: Direction; -} - export interface PageInfoTimeline { pageIndex: number; @@ -140,6 +134,8 @@ export interface TimelineInput { kqlQuery?: Maybe; + indexNames?: Maybe; + title?: Maybe; templateTimelineId?: Maybe; @@ -356,10 +352,6 @@ export enum NetworkDnsFields { dnsBytesOut = 'dnsBytesOut', } -export enum TlsFields { - _id = '_id', -} - export enum DataProviderType { default = 'default', template = 'template', @@ -425,10 +417,6 @@ export enum FlowDirection { biDirectional = 'biDirectional', } -export type ToStringArrayNoNullable = any; - -export type ToIFieldSubTypeNonNullable = any; - export type ToStringArray = string[] | string; export type Date = string; @@ -443,6 +431,10 @@ export type ToAny = any; export type EsValue = any; +export type ToStringArrayNoNullable = any; + +export type ToIFieldSubTypeNonNullable = any; + // ==================================================== // Scalars // ==================================================== @@ -566,14 +558,6 @@ export interface Source { NetworkDnsHistogram: NetworkDsOverTimeData; NetworkHttp: NetworkHttpData; - - OverviewNetwork?: Maybe; - - OverviewHost?: Maybe; - - Tls: TlsData; - /** Gets UncommonProcesses based on a timerange, or all UncommonProcesses if no criteria is specified */ - UncommonProcesses: UncommonProcessesData; /** Just a simple example to get the app name */ whoAmI?: Maybe; } @@ -605,33 +589,7 @@ export interface SourceStatus { /** Whether the configured alias or wildcard pattern resolve to any auditbeat indices */ indicesExist: boolean; /** The list of fields defined in the index mappings */ - indexFields: IndexField[]; -} - -/** A descriptor of a field in an index */ -export interface IndexField { - /** Where the field belong */ - category: string; - /** Example of field's value */ - example?: Maybe; - /** whether the field's belong to an alias index */ - indexes: (Maybe)[]; - /** The name of the field */ - name: string; - /** The type of the field's values as recognized by Kibana */ - type: string; - /** Whether the field's values can be efficiently searched for */ - searchable: boolean; - /** Whether the field's values can be aggregated */ - aggregatable: boolean; - /** Description of the field */ - description?: Maybe; - - format?: Maybe; - /** the elastic type as mapped in the index */ - esTypes?: Maybe; - - subType?: Maybe; + indexFields: string[]; } export interface AuthenticationsData { @@ -1872,122 +1830,6 @@ export interface NetworkHttpItem { statuses: string[]; } -export interface OverviewNetworkData { - auditbeatSocket?: Maybe; - - filebeatCisco?: Maybe; - - filebeatNetflow?: Maybe; - - filebeatPanw?: Maybe; - - filebeatSuricata?: Maybe; - - filebeatZeek?: Maybe; - - packetbeatDNS?: Maybe; - - packetbeatFlow?: Maybe; - - packetbeatTLS?: Maybe; - - inspect?: Maybe; -} - -export interface OverviewHostData { - auditbeatAuditd?: Maybe; - - auditbeatFIM?: Maybe; - - auditbeatLogin?: Maybe; - - auditbeatPackage?: Maybe; - - auditbeatProcess?: Maybe; - - auditbeatUser?: Maybe; - - endgameDns?: Maybe; - - endgameFile?: Maybe; - - endgameImageLoad?: Maybe; - - endgameNetwork?: Maybe; - - endgameProcess?: Maybe; - - endgameRegistry?: Maybe; - - endgameSecurity?: Maybe; - - filebeatSystemModule?: Maybe; - - winlogbeatSecurity?: Maybe; - - winlogbeatMWSysmonOperational?: Maybe; - - inspect?: Maybe; -} - -export interface TlsData { - edges: TlsEdges[]; - - totalCount: number; - - pageInfo: PageInfoPaginated; - - inspect?: Maybe; -} - -export interface TlsEdges { - node: TlsNode; - - cursor: CursorType; -} - -export interface TlsNode { - _id?: Maybe; - - timestamp?: Maybe; - - notAfter?: Maybe; - - subjects?: Maybe; - - ja3?: Maybe; - - issuers?: Maybe; -} - -export interface UncommonProcessesData { - edges: UncommonProcessesEdges[]; - - totalCount: number; - - pageInfo: PageInfoPaginated; - - inspect?: Maybe; -} - -export interface UncommonProcessesEdges { - node: UncommonProcessItem; - - cursor: CursorType; -} - -export interface UncommonProcessItem { - _id: string; - - instances: number; - - process: ProcessEcsFields; - - hosts: HostEcsFields[]; - - user?: Maybe; -} - export interface SayMyName { /** The id of the source */ appName: string; @@ -2020,6 +1862,8 @@ export interface TimelineResult { kqlQuery?: Maybe; + indexNames?: Maybe; + notes?: Maybe; noteIds?: Maybe; @@ -2292,6 +2136,32 @@ export interface HostFields { type?: Maybe; } +/** A descriptor of a field in an index */ +export interface IndexField { + /** Where the field belong */ + category: string; + /** Example of field's value */ + example?: Maybe; + /** whether the field's belong to an alias index */ + indexes: (Maybe)[]; + /** The name of the field */ + name: string; + /** The type of the field's values as recognized by Kibana */ + type: string; + /** Whether the field's values can be efficiently searched for */ + searchable: boolean; + /** Whether the field's values can be aggregated */ + aggregatable: boolean; + /** Description of the field */ + description?: Maybe; + + format?: Maybe; + /** the elastic type as mapped in the index */ + esTypes?: Maybe; + + subType?: Maybe; +} + // ==================================================== // Arguments // ==================================================== @@ -2557,50 +2427,6 @@ export interface NetworkHttpSourceArgs { defaultIndex: string[]; } -export interface OverviewNetworkSourceArgs { - id?: Maybe; - - timerange: TimerangeInput; - - filterQuery?: Maybe; - - defaultIndex: string[]; -} -export interface OverviewHostSourceArgs { - id?: Maybe; - - timerange: TimerangeInput; - - filterQuery?: Maybe; - - defaultIndex: string[]; -} -export interface TlsSourceArgs { - filterQuery?: Maybe; - - id?: Maybe; - - ip: string; - - pagination: PaginationInputPaginated; - - sort: TlsSortField; - - flowTarget: FlowTargetSourceDest; - - timerange: TimerangeInput; - - defaultIndex: string[]; -} -export interface UncommonProcessesSourceArgs { - timerange: TimerangeInput; - - pagination: PaginationInputPaginated; - - filterQuery?: Maybe; - - defaultIndex: string[]; -} export interface IndicesExistSourceStatusArgs { defaultIndex: string[]; } @@ -3037,14 +2863,6 @@ export namespace SourceResolvers { NetworkDnsHistogram?: NetworkDnsHistogramResolver; NetworkHttp?: NetworkHttpResolver; - - OverviewNetwork?: OverviewNetworkResolver, TypeParent, TContext>; - - OverviewHost?: OverviewHostResolver, TypeParent, TContext>; - - Tls?: TlsResolver; - /** Gets UncommonProcesses based on a timerange, or all UncommonProcesses if no criteria is specified */ - UncommonProcesses?: UncommonProcessesResolver; /** Just a simple example to get the app name */ whoAmI?: WhoAmIResolver, TypeParent, TContext>; } @@ -3396,75 +3214,6 @@ export namespace SourceResolvers { defaultIndex: string[]; } - export type OverviewNetworkResolver< - R = Maybe, - Parent = Source, - TContext = SiemContext - > = Resolver; - export interface OverviewNetworkArgs { - id?: Maybe; - - timerange: TimerangeInput; - - filterQuery?: Maybe; - - defaultIndex: string[]; - } - - export type OverviewHostResolver< - R = Maybe, - Parent = Source, - TContext = SiemContext - > = Resolver; - export interface OverviewHostArgs { - id?: Maybe; - - timerange: TimerangeInput; - - filterQuery?: Maybe; - - defaultIndex: string[]; - } - - export type TlsResolver = Resolver< - R, - Parent, - TContext, - TlsArgs - >; - export interface TlsArgs { - filterQuery?: Maybe; - - id?: Maybe; - - ip: string; - - pagination: PaginationInputPaginated; - - sort: TlsSortField; - - flowTarget: FlowTargetSourceDest; - - timerange: TimerangeInput; - - defaultIndex: string[]; - } - - export type UncommonProcessesResolver< - R = UncommonProcessesData, - Parent = Source, - TContext = SiemContext - > = Resolver; - export interface UncommonProcessesArgs { - timerange: TimerangeInput; - - pagination: PaginationInputPaginated; - - filterQuery?: Maybe; - - defaultIndex: string[]; - } - export type WhoAmIResolver< R = Maybe, Parent = Source, @@ -3538,7 +3287,7 @@ export namespace SourceStatusResolvers { /** Whether the configured alias or wildcard pattern resolve to any auditbeat indices */ indicesExist?: IndicesExistResolver; /** The list of fields defined in the index mappings */ - indexFields?: IndexFieldsResolver; + indexFields?: IndexFieldsResolver; } export type IndicesExistResolver< @@ -3551,7 +3300,7 @@ export namespace SourceStatusResolvers { } export type IndexFieldsResolver< - R = IndexField[], + R = string[], Parent = SourceStatus, TContext = SiemContext > = Resolver; @@ -3559,89 +3308,6 @@ export namespace SourceStatusResolvers { defaultIndex: string[]; } } -/** A descriptor of a field in an index */ -export namespace IndexFieldResolvers { - export interface Resolvers { - /** Where the field belong */ - category?: CategoryResolver; - /** Example of field's value */ - example?: ExampleResolver, TypeParent, TContext>; - /** whether the field's belong to an alias index */ - indexes?: IndexesResolver<(Maybe)[], TypeParent, TContext>; - /** The name of the field */ - name?: NameResolver; - /** The type of the field's values as recognized by Kibana */ - type?: TypeResolver; - /** Whether the field's values can be efficiently searched for */ - searchable?: SearchableResolver; - /** Whether the field's values can be aggregated */ - aggregatable?: AggregatableResolver; - /** Description of the field */ - description?: DescriptionResolver, TypeParent, TContext>; - - format?: FormatResolver, TypeParent, TContext>; - /** the elastic type as mapped in the index */ - esTypes?: EsTypesResolver, TypeParent, TContext>; - - subType?: SubTypeResolver, TypeParent, TContext>; - } - - export type CategoryResolver = Resolver< - R, - Parent, - TContext - >; - export type ExampleResolver< - R = Maybe, - Parent = IndexField, - TContext = SiemContext - > = Resolver; - export type IndexesResolver< - R = (Maybe)[], - Parent = IndexField, - TContext = SiemContext - > = Resolver; - export type NameResolver = Resolver< - R, - Parent, - TContext - >; - export type TypeResolver = Resolver< - R, - Parent, - TContext - >; - export type SearchableResolver< - R = boolean, - Parent = IndexField, - TContext = SiemContext - > = Resolver; - export type AggregatableResolver< - R = boolean, - Parent = IndexField, - TContext = SiemContext - > = Resolver; - export type DescriptionResolver< - R = Maybe, - Parent = IndexField, - TContext = SiemContext - > = Resolver; - export type FormatResolver< - R = Maybe, - Parent = IndexField, - TContext = SiemContext - > = Resolver; - export type EsTypesResolver< - R = Maybe, - Parent = IndexField, - TContext = SiemContext - > = Resolver; - export type SubTypeResolver< - R = Maybe, - Parent = IndexField, - TContext = SiemContext - > = Resolver; -} export namespace AuthenticationsDataResolvers { export interface Resolvers { @@ -7818,400 +7484,6 @@ export namespace NetworkHttpItemResolvers { > = Resolver; } -export namespace OverviewNetworkDataResolvers { - export interface Resolvers { - auditbeatSocket?: AuditbeatSocketResolver, TypeParent, TContext>; - - filebeatCisco?: FilebeatCiscoResolver, TypeParent, TContext>; - - filebeatNetflow?: FilebeatNetflowResolver, TypeParent, TContext>; - - filebeatPanw?: FilebeatPanwResolver, TypeParent, TContext>; - - filebeatSuricata?: FilebeatSuricataResolver, TypeParent, TContext>; - - filebeatZeek?: FilebeatZeekResolver, TypeParent, TContext>; - - packetbeatDNS?: PacketbeatDnsResolver, TypeParent, TContext>; - - packetbeatFlow?: PacketbeatFlowResolver, TypeParent, TContext>; - - packetbeatTLS?: PacketbeatTlsResolver, TypeParent, TContext>; - - inspect?: InspectResolver, TypeParent, TContext>; - } - - export type AuditbeatSocketResolver< - R = Maybe, - Parent = OverviewNetworkData, - TContext = SiemContext - > = Resolver; - export type FilebeatCiscoResolver< - R = Maybe, - Parent = OverviewNetworkData, - TContext = SiemContext - > = Resolver; - export type FilebeatNetflowResolver< - R = Maybe, - Parent = OverviewNetworkData, - TContext = SiemContext - > = Resolver; - export type FilebeatPanwResolver< - R = Maybe, - Parent = OverviewNetworkData, - TContext = SiemContext - > = Resolver; - export type FilebeatSuricataResolver< - R = Maybe, - Parent = OverviewNetworkData, - TContext = SiemContext - > = Resolver; - export type FilebeatZeekResolver< - R = Maybe, - Parent = OverviewNetworkData, - TContext = SiemContext - > = Resolver; - export type PacketbeatDnsResolver< - R = Maybe, - Parent = OverviewNetworkData, - TContext = SiemContext - > = Resolver; - export type PacketbeatFlowResolver< - R = Maybe, - Parent = OverviewNetworkData, - TContext = SiemContext - > = Resolver; - export type PacketbeatTlsResolver< - R = Maybe, - Parent = OverviewNetworkData, - TContext = SiemContext - > = Resolver; - export type InspectResolver< - R = Maybe, - Parent = OverviewNetworkData, - TContext = SiemContext - > = Resolver; -} - -export namespace OverviewHostDataResolvers { - export interface Resolvers { - auditbeatAuditd?: AuditbeatAuditdResolver, TypeParent, TContext>; - - auditbeatFIM?: AuditbeatFimResolver, TypeParent, TContext>; - - auditbeatLogin?: AuditbeatLoginResolver, TypeParent, TContext>; - - auditbeatPackage?: AuditbeatPackageResolver, TypeParent, TContext>; - - auditbeatProcess?: AuditbeatProcessResolver, TypeParent, TContext>; - - auditbeatUser?: AuditbeatUserResolver, TypeParent, TContext>; - - endgameDns?: EndgameDnsResolver, TypeParent, TContext>; - - endgameFile?: EndgameFileResolver, TypeParent, TContext>; - - endgameImageLoad?: EndgameImageLoadResolver, TypeParent, TContext>; - - endgameNetwork?: EndgameNetworkResolver, TypeParent, TContext>; - - endgameProcess?: EndgameProcessResolver, TypeParent, TContext>; - - endgameRegistry?: EndgameRegistryResolver, TypeParent, TContext>; - - endgameSecurity?: EndgameSecurityResolver, TypeParent, TContext>; - - filebeatSystemModule?: FilebeatSystemModuleResolver, TypeParent, TContext>; - - winlogbeatSecurity?: WinlogbeatSecurityResolver, TypeParent, TContext>; - - winlogbeatMWSysmonOperational?: WinlogbeatMwSysmonOperationalResolver< - Maybe, - TypeParent, - TContext - >; - - inspect?: InspectResolver, TypeParent, TContext>; - } - - export type AuditbeatAuditdResolver< - R = Maybe, - Parent = OverviewHostData, - TContext = SiemContext - > = Resolver; - export type AuditbeatFimResolver< - R = Maybe, - Parent = OverviewHostData, - TContext = SiemContext - > = Resolver; - export type AuditbeatLoginResolver< - R = Maybe, - Parent = OverviewHostData, - TContext = SiemContext - > = Resolver; - export type AuditbeatPackageResolver< - R = Maybe, - Parent = OverviewHostData, - TContext = SiemContext - > = Resolver; - export type AuditbeatProcessResolver< - R = Maybe, - Parent = OverviewHostData, - TContext = SiemContext - > = Resolver; - export type AuditbeatUserResolver< - R = Maybe, - Parent = OverviewHostData, - TContext = SiemContext - > = Resolver; - export type EndgameDnsResolver< - R = Maybe, - Parent = OverviewHostData, - TContext = SiemContext - > = Resolver; - export type EndgameFileResolver< - R = Maybe, - Parent = OverviewHostData, - TContext = SiemContext - > = Resolver; - export type EndgameImageLoadResolver< - R = Maybe, - Parent = OverviewHostData, - TContext = SiemContext - > = Resolver; - export type EndgameNetworkResolver< - R = Maybe, - Parent = OverviewHostData, - TContext = SiemContext - > = Resolver; - export type EndgameProcessResolver< - R = Maybe, - Parent = OverviewHostData, - TContext = SiemContext - > = Resolver; - export type EndgameRegistryResolver< - R = Maybe, - Parent = OverviewHostData, - TContext = SiemContext - > = Resolver; - export type EndgameSecurityResolver< - R = Maybe, - Parent = OverviewHostData, - TContext = SiemContext - > = Resolver; - export type FilebeatSystemModuleResolver< - R = Maybe, - Parent = OverviewHostData, - TContext = SiemContext - > = Resolver; - export type WinlogbeatSecurityResolver< - R = Maybe, - Parent = OverviewHostData, - TContext = SiemContext - > = Resolver; - export type WinlogbeatMwSysmonOperationalResolver< - R = Maybe, - Parent = OverviewHostData, - TContext = SiemContext - > = Resolver; - export type InspectResolver< - R = Maybe, - Parent = OverviewHostData, - TContext = SiemContext - > = Resolver; -} - -export namespace TlsDataResolvers { - export interface Resolvers { - edges?: EdgesResolver; - - totalCount?: TotalCountResolver; - - pageInfo?: PageInfoResolver; - - inspect?: InspectResolver, TypeParent, TContext>; - } - - export type EdgesResolver = Resolver< - R, - Parent, - TContext - >; - export type TotalCountResolver = Resolver< - R, - Parent, - TContext - >; - export type PageInfoResolver< - R = PageInfoPaginated, - Parent = TlsData, - TContext = SiemContext - > = Resolver; - export type InspectResolver< - R = Maybe, - Parent = TlsData, - TContext = SiemContext - > = Resolver; -} - -export namespace TlsEdgesResolvers { - export interface Resolvers { - node?: NodeResolver; - - cursor?: CursorResolver; - } - - export type NodeResolver = Resolver< - R, - Parent, - TContext - >; - export type CursorResolver = Resolver< - R, - Parent, - TContext - >; -} - -export namespace TlsNodeResolvers { - export interface Resolvers { - _id?: _IdResolver, TypeParent, TContext>; - - timestamp?: TimestampResolver, TypeParent, TContext>; - - notAfter?: NotAfterResolver, TypeParent, TContext>; - - subjects?: SubjectsResolver, TypeParent, TContext>; - - ja3?: Ja3Resolver, TypeParent, TContext>; - - issuers?: IssuersResolver, TypeParent, TContext>; - } - - export type _IdResolver, Parent = TlsNode, TContext = SiemContext> = Resolver< - R, - Parent, - TContext - >; - export type TimestampResolver< - R = Maybe, - Parent = TlsNode, - TContext = SiemContext - > = Resolver; - export type NotAfterResolver< - R = Maybe, - Parent = TlsNode, - TContext = SiemContext - > = Resolver; - export type SubjectsResolver< - R = Maybe, - Parent = TlsNode, - TContext = SiemContext - > = Resolver; - export type Ja3Resolver, Parent = TlsNode, TContext = SiemContext> = Resolver< - R, - Parent, - TContext - >; - export type IssuersResolver< - R = Maybe, - Parent = TlsNode, - TContext = SiemContext - > = Resolver; -} - -export namespace UncommonProcessesDataResolvers { - export interface Resolvers { - edges?: EdgesResolver; - - totalCount?: TotalCountResolver; - - pageInfo?: PageInfoResolver; - - inspect?: InspectResolver, TypeParent, TContext>; - } - - export type EdgesResolver< - R = UncommonProcessesEdges[], - Parent = UncommonProcessesData, - TContext = SiemContext - > = Resolver; - export type TotalCountResolver< - R = number, - Parent = UncommonProcessesData, - TContext = SiemContext - > = Resolver; - export type PageInfoResolver< - R = PageInfoPaginated, - Parent = UncommonProcessesData, - TContext = SiemContext - > = Resolver; - export type InspectResolver< - R = Maybe, - Parent = UncommonProcessesData, - TContext = SiemContext - > = Resolver; -} - -export namespace UncommonProcessesEdgesResolvers { - export interface Resolvers { - node?: NodeResolver; - - cursor?: CursorResolver; - } - - export type NodeResolver< - R = UncommonProcessItem, - Parent = UncommonProcessesEdges, - TContext = SiemContext - > = Resolver; - export type CursorResolver< - R = CursorType, - Parent = UncommonProcessesEdges, - TContext = SiemContext - > = Resolver; -} - -export namespace UncommonProcessItemResolvers { - export interface Resolvers { - _id?: _IdResolver; - - instances?: InstancesResolver; - - process?: ProcessResolver; - - hosts?: HostsResolver; - - user?: UserResolver, TypeParent, TContext>; - } - - export type _IdResolver< - R = string, - Parent = UncommonProcessItem, - TContext = SiemContext - > = Resolver; - export type InstancesResolver< - R = number, - Parent = UncommonProcessItem, - TContext = SiemContext - > = Resolver; - export type ProcessResolver< - R = ProcessEcsFields, - Parent = UncommonProcessItem, - TContext = SiemContext - > = Resolver; - export type HostsResolver< - R = HostEcsFields[], - Parent = UncommonProcessItem, - TContext = SiemContext - > = Resolver; - export type UserResolver< - R = Maybe, - Parent = UncommonProcessItem, - TContext = SiemContext - > = Resolver; -} - export namespace SayMyNameResolvers { export interface Resolvers { /** The id of the source */ @@ -8257,6 +7529,8 @@ export namespace TimelineResultResolvers { kqlQuery?: KqlQueryResolver, TypeParent, TContext>; + indexNames?: IndexNamesResolver, TypeParent, TContext>; + notes?: NotesResolver, TypeParent, TContext>; noteIds?: NoteIdsResolver, TypeParent, TContext>; @@ -8357,6 +7631,11 @@ export namespace TimelineResultResolvers { Parent = TimelineResult, TContext = SiemContext > = Resolver; + export type IndexNamesResolver< + R = Maybe, + Parent = TimelineResult, + TContext = SiemContext + > = Resolver; export type NotesResolver< R = Maybe, Parent = TimelineResult, @@ -9305,6 +8584,89 @@ export namespace HostFieldsResolvers { TContext = SiemContext > = Resolver; } +/** A descriptor of a field in an index */ +export namespace IndexFieldResolvers { + export interface Resolvers { + /** Where the field belong */ + category?: CategoryResolver; + /** Example of field's value */ + example?: ExampleResolver, TypeParent, TContext>; + /** whether the field's belong to an alias index */ + indexes?: IndexesResolver<(Maybe)[], TypeParent, TContext>; + /** The name of the field */ + name?: NameResolver; + /** The type of the field's values as recognized by Kibana */ + type?: TypeResolver; + /** Whether the field's values can be efficiently searched for */ + searchable?: SearchableResolver; + /** Whether the field's values can be aggregated */ + aggregatable?: AggregatableResolver; + /** Description of the field */ + description?: DescriptionResolver, TypeParent, TContext>; + + format?: FormatResolver, TypeParent, TContext>; + /** the elastic type as mapped in the index */ + esTypes?: EsTypesResolver, TypeParent, TContext>; + + subType?: SubTypeResolver, TypeParent, TContext>; + } + + export type CategoryResolver = Resolver< + R, + Parent, + TContext + >; + export type ExampleResolver< + R = Maybe, + Parent = IndexField, + TContext = SiemContext + > = Resolver; + export type IndexesResolver< + R = (Maybe)[], + Parent = IndexField, + TContext = SiemContext + > = Resolver; + export type NameResolver = Resolver< + R, + Parent, + TContext + >; + export type TypeResolver = Resolver< + R, + Parent, + TContext + >; + export type SearchableResolver< + R = boolean, + Parent = IndexField, + TContext = SiemContext + > = Resolver; + export type AggregatableResolver< + R = boolean, + Parent = IndexField, + TContext = SiemContext + > = Resolver; + export type DescriptionResolver< + R = Maybe, + Parent = IndexField, + TContext = SiemContext + > = Resolver; + export type FormatResolver< + R = Maybe, + Parent = IndexField, + TContext = SiemContext + > = Resolver; + export type EsTypesResolver< + R = Maybe, + Parent = IndexField, + TContext = SiemContext + > = Resolver; + export type SubTypeResolver< + R = Maybe, + Parent = IndexField, + TContext = SiemContext + > = Resolver; +} /** Directs the executor to skip this field or fragment when the `if` argument is true. */ export type SkipDirectiveResolver = DirectiveResolverFn< @@ -9339,14 +8701,6 @@ export interface DeprecatedDirectiveArgs { reason?: string; } -export interface ToStringArrayNoNullableScalarConfig - extends GraphQLScalarTypeConfig { - name: 'ToStringArrayNoNullable'; -} -export interface ToIFieldSubTypeNonNullableScalarConfig - extends GraphQLScalarTypeConfig { - name: 'ToIFieldSubTypeNonNullable'; -} export interface ToStringArrayScalarConfig extends GraphQLScalarTypeConfig { name: 'ToStringArray'; } @@ -9368,6 +8722,14 @@ export interface ToAnyScalarConfig extends GraphQLScalarTypeConfig { export interface EsValueScalarConfig extends GraphQLScalarTypeConfig { name: 'EsValue'; } +export interface ToStringArrayNoNullableScalarConfig + extends GraphQLScalarTypeConfig { + name: 'ToStringArrayNoNullable'; +} +export interface ToIFieldSubTypeNonNullableScalarConfig + extends GraphQLScalarTypeConfig { + name: 'ToIFieldSubTypeNonNullable'; +} export type IResolvers = { Query?: QueryResolvers.Resolvers; @@ -9378,7 +8740,6 @@ export type IResolvers = { SourceConfiguration?: SourceConfigurationResolvers.Resolvers; SourceFields?: SourceFieldsResolvers.Resolvers; SourceStatus?: SourceStatusResolvers.Resolvers; - IndexField?: IndexFieldResolvers.Resolvers; AuthenticationsData?: AuthenticationsDataResolvers.Resolvers; AuthenticationsEdges?: AuthenticationsEdgesResolvers.Resolvers; AuthenticationItem?: AuthenticationItemResolvers.Resolvers; @@ -9490,14 +8851,6 @@ export type IResolvers = { NetworkHttpData?: NetworkHttpDataResolvers.Resolvers; NetworkHttpEdges?: NetworkHttpEdgesResolvers.Resolvers; NetworkHttpItem?: NetworkHttpItemResolvers.Resolvers; - OverviewNetworkData?: OverviewNetworkDataResolvers.Resolvers; - OverviewHostData?: OverviewHostDataResolvers.Resolvers; - TlsData?: TlsDataResolvers.Resolvers; - TlsEdges?: TlsEdgesResolvers.Resolvers; - TlsNode?: TlsNodeResolvers.Resolvers; - UncommonProcessesData?: UncommonProcessesDataResolvers.Resolvers; - UncommonProcessesEdges?: UncommonProcessesEdgesResolvers.Resolvers; - UncommonProcessItem?: UncommonProcessItemResolvers.Resolvers; SayMyName?: SayMyNameResolvers.Resolvers; TimelineResult?: TimelineResultResolvers.Resolvers; ColumnHeaderResult?: ColumnHeaderResultResolvers.Resolvers; @@ -9520,8 +8873,7 @@ export type IResolvers = { EventsTimelineData?: EventsTimelineDataResolvers.Resolvers; OsFields?: OsFieldsResolvers.Resolvers; HostFields?: HostFieldsResolvers.Resolvers; - ToStringArrayNoNullable?: GraphQLScalarType; - ToIFieldSubTypeNonNullable?: GraphQLScalarType; + IndexField?: IndexFieldResolvers.Resolvers; ToStringArray?: GraphQLScalarType; Date?: GraphQLScalarType; ToNumberArray?: GraphQLScalarType; @@ -9529,6 +8881,8 @@ export type IResolvers = { ToBooleanArray?: GraphQLScalarType; ToAny?: GraphQLScalarType; EsValue?: GraphQLScalarType; + ToStringArrayNoNullable?: GraphQLScalarType; + ToIFieldSubTypeNonNullable?: GraphQLScalarType; } & { [typeName: string]: never }; export type IDirectiveResolvers = { diff --git a/x-pack/plugins/security_solution/server/graphql/uncommon_processes/index.ts b/x-pack/plugins/security_solution/server/graphql/uncommon_processes/index.ts deleted file mode 100644 index d0da0efd8a560..0000000000000 --- a/x-pack/plugins/security_solution/server/graphql/uncommon_processes/index.ts +++ /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; - * you may not use this file except in compliance with the Elastic License. - */ - -export { createUncommonProcessesResolvers } from './resolvers'; -export { uncommonProcessesSchema } from './schema.gql'; diff --git a/x-pack/plugins/security_solution/server/graphql/uncommon_processes/resolvers.ts b/x-pack/plugins/security_solution/server/graphql/uncommon_processes/resolvers.ts deleted file mode 100644 index 03d3c3d1a1fe4..0000000000000 --- a/x-pack/plugins/security_solution/server/graphql/uncommon_processes/resolvers.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { SourceResolvers } from '../../graphql/types'; -import { AppResolverOf, ChildResolverOf } from '../../lib/framework'; -import { UncommonProcesses } from '../../lib/uncommon_processes'; -import { createOptionsPaginated } from '../../utils/build_query/create_options'; -import { QuerySourceResolver } from '../sources/resolvers'; - -type QueryUncommonProcessesResolver = ChildResolverOf< - AppResolverOf, - QuerySourceResolver ->; - -export interface UncommonProcessesResolversDeps { - uncommonProcesses: UncommonProcesses; -} - -export const createUncommonProcessesResolvers = ( - libs: UncommonProcessesResolversDeps -): { - Source: { - UncommonProcesses: QueryUncommonProcessesResolver; - }; -} => ({ - Source: { - async UncommonProcesses(source, args, { req }, info) { - const options = createOptionsPaginated(source, args, info); - return libs.uncommonProcesses.getUncommonProcesses(req, options); - }, - }, -}); diff --git a/x-pack/plugins/security_solution/server/graphql/uncommon_processes/schema.gql.ts b/x-pack/plugins/security_solution/server/graphql/uncommon_processes/schema.gql.ts deleted file mode 100644 index 36a3da6779172..0000000000000 --- a/x-pack/plugins/security_solution/server/graphql/uncommon_processes/schema.gql.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import gql from 'graphql-tag'; - -export const uncommonProcessesSchema = gql` - type UncommonProcessItem { - _id: String! - instances: Float! - process: ProcessEcsFields! - hosts: [HostEcsFields!]! - user: UserEcsFields - } - - type UncommonProcessesEdges { - node: UncommonProcessItem! - cursor: CursorType! - } - - type UncommonProcessesData { - edges: [UncommonProcessesEdges!]! - totalCount: Float! - pageInfo: PageInfoPaginated! - inspect: Inspect - } - - extend type Source { - "Gets UncommonProcesses based on a timerange, or all UncommonProcesses if no criteria is specified" - UncommonProcesses( - timerange: TimerangeInput! - pagination: PaginationInputPaginated! - filterQuery: String - defaultIndex: [String!]! - ): UncommonProcessesData! - } -`; diff --git a/x-pack/plugins/security_solution/server/init_server.ts b/x-pack/plugins/security_solution/server/init_server.ts index 1463d7f0da284..ac0273ec1770d 100644 --- a/x-pack/plugins/security_solution/server/init_server.ts +++ b/x-pack/plugins/security_solution/server/init_server.ts @@ -16,7 +16,6 @@ import { createKpiNetworkResolvers } from './graphql/kpi_network'; import { createNetworkResolvers } from './graphql/network'; import { createNoteResolvers } from './graphql/note'; import { createPinnedEventResolvers } from './graphql/pinned_event'; -import { createOverviewResolvers } from './graphql/overview'; import { createScalarDateResolvers } from './graphql/scalar_date'; import { createScalarToAnyValueResolvers } from './graphql/scalar_to_any'; import { createScalarToBooleanArrayValueResolvers } from './graphql/scalar_to_boolean_array'; @@ -25,10 +24,8 @@ import { createScalarToNumberArrayValueResolvers } from './graphql/scalar_to_num import { createSourceStatusResolvers } from './graphql/source_status'; import { createSourcesResolvers } from './graphql/sources'; import { createTimelineResolvers } from './graphql/timeline'; -import { createUncommonProcessesResolvers } from './graphql/uncommon_processes'; import { createWhoAmIResolvers } from './graphql/who_am_i'; import { AppBackendLibs } from './lib/types'; -import { createTlsResolvers } from './graphql/tls'; import { createMatrixHistogramResolvers } from './graphql/matrix_histogram'; export const initServer = (libs: AppBackendLibs) => { @@ -45,7 +42,6 @@ export const initServer = (libs: AppBackendLibs) => { createPinnedEventResolvers(libs) as IResolvers, createSourcesResolvers(libs) as IResolvers, createScalarToStringArrayValueResolvers() as IResolvers, - createOverviewResolvers(libs) as IResolvers, createNetworkResolvers(libs) as IResolvers, createScalarDateResolvers() as IResolvers, createScalarToDateArrayValueResolvers() as IResolvers, @@ -55,8 +51,6 @@ export const initServer = (libs: AppBackendLibs) => { createSourcesResolvers(libs) as IResolvers, createSourceStatusResolvers(libs) as IResolvers, createTimelineResolvers(libs) as IResolvers, - createTlsResolvers(libs) as IResolvers, - createUncommonProcessesResolvers(libs) as IResolvers, createWhoAmIResolvers() as IResolvers, createKpiHostsResolvers(libs) as IResolvers, ], diff --git a/x-pack/plugins/security_solution/server/lib/compose/kibana.ts b/x-pack/plugins/security_solution/server/lib/compose/kibana.ts index db76f6d52dbb0..3bfb3d9492353 100644 --- a/x-pack/plugins/security_solution/server/lib/compose/kibana.ts +++ b/x-pack/plugins/security_solution/server/lib/compose/kibana.ts @@ -17,17 +17,13 @@ import { ElasticsearchKpiHostsAdapter } from '../kpi_hosts/elasticsearch_adapter import { ElasticsearchIndexFieldAdapter, IndexFields } from '../index_fields'; import { ElasticsearchIpDetailsAdapter, IpDetails } from '../ip_details'; -import { ElasticsearchTlsAdapter, TLS } from '../tls'; import { KpiNetwork } from '../kpi_network'; import { ElasticsearchKpiNetworkAdapter } from '../kpi_network/elasticsearch_adapter'; import { ElasticsearchNetworkAdapter, Network } from '../network'; -import { Overview } from '../overview'; -import { ElasticsearchOverviewAdapter } from '../overview/elasticsearch_adapter'; import { ElasticsearchSourceStatusAdapter, SourceStatus } from '../source_status'; import { ConfigurationSourcesAdapter, Sources } from '../sources'; import { AppBackendLibs, AppDomainLibs } from '../types'; -import { ElasticsearchUncommonProcessesAdapter, UncommonProcesses } from '../uncommon_processes'; import * as note from '../note/saved_object'; import * as pinnedEvent from '../pinned_event/saved_object'; import * as timeline from '../timeline/saved_object'; @@ -47,16 +43,13 @@ export function compose( const domainLibs: AppDomainLibs = { authentications: new Authentications(new ElasticsearchAuthenticationAdapter(framework)), events: new Events(new ElasticsearchEventsAdapter(framework)), - fields: new IndexFields(new ElasticsearchIndexFieldAdapter(framework)), + fields: new IndexFields(new ElasticsearchIndexFieldAdapter()), hosts: new Hosts(new ElasticsearchHostsAdapter(framework, endpointContext)), ipDetails: new IpDetails(new ElasticsearchIpDetailsAdapter(framework)), - tls: new TLS(new ElasticsearchTlsAdapter(framework)), kpiHosts: new KpiHosts(new ElasticsearchKpiHostsAdapter(framework)), kpiNetwork: new KpiNetwork(new ElasticsearchKpiNetworkAdapter(framework)), matrixHistogram: new MatrixHistogram(new ElasticsearchMatrixHistogramAdapter(framework)), network: new Network(new ElasticsearchNetworkAdapter(framework)), - overview: new Overview(new ElasticsearchOverviewAdapter(framework)), - uncommonProcesses: new UncommonProcesses(new ElasticsearchUncommonProcessesAdapter(framework)), }; const libs: AppBackendLibs = { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts index bbdb8ea0a36ed..9ee8c5cf298a1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts @@ -337,7 +337,7 @@ export const repeatedSearchResultsWithSortId = ( guids: string[], ips?: string[], destIps?: string[] -) => ({ +): SignalSearchResponse => ({ took: 10, timed_out: false, _shards: { @@ -364,7 +364,7 @@ export const repeatedSearchResultsWithNoSortId = ( pageSize: number, guids: string[], ips?: string[] -) => ({ +): SignalSearchResponse => ({ took: 10, timed_out: false, _shards: { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_ml_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_ml_signals.ts index bd9bf50688b58..89e3d28f451e4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_ml_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_ml_signals.ts @@ -8,7 +8,7 @@ import dateMath from '@elastic/datemath'; import { KibanaRequest } from '../../../../../../../src/core/server'; import { MlPluginSetup } from '../../../../../ml/server'; -import { getAnomalies } from '../../machine_learning'; +import { AnomalyResults, getAnomalies } from '../../machine_learning'; export const findMlSignals = async ({ ml, @@ -24,7 +24,7 @@ export const findMlSignals = async ({ anomalyThreshold: number; from: string; to: string; -}) => { +}): Promise => { const { mlAnomalySearch } = ml.mlSystemProvider(request); const params = { jobIds: [jobId], @@ -32,7 +32,5 @@ export const findMlSignals = async ({ earliestMs: dateMath.parse(from)?.valueOf() ?? 0, latestMs: dateMath.parse(to)?.valueOf() ?? 0, }; - const relevantAnomalies = await getAnomalies(params, mlAnomalySearch); - - return relevantAnomalies; + return getAnomalies(params, mlAnomalySearch); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_threshold_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_threshold_signals.ts index 251c043adb58b..604b452174045 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_threshold_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_threshold_signals.ts @@ -34,6 +34,7 @@ export const findThresholdSignals = async ({ }: FindThresholdSignalsParams): Promise<{ searchResult: SignalSearchResponse; searchDuration: string; + searchErrors: string[]; }> => { const aggregations = threshold && !isEmpty(threshold.field) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts index 756aedd5273d3..d369a91335347 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts @@ -3,56 +3,18 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import moment from 'moment'; -import { AlertServices } from '../../../../../alerts/server'; -import { ListClient } from '../../../../../lists/server'; -import { RuleAlertAction } from '../../../../common/detection_engine/types'; -import { RuleTypeParams, RefreshTypes } from '../types'; -import { Logger } from '../../../../../../../src/core/server'; import { singleSearchAfter } from './single_search_after'; import { singleBulkCreate } from './single_bulk_create'; -import { BuildRuleMessage } from './rule_messages'; -import { SignalSearchResponse } from './types'; import { filterEventsAgainstList } from './filter_events_with_list'; -import { ExceptionListItemSchema } from '../../../../../lists/common/schemas'; -import { getSignalTimeTuples } from './utils'; - -interface SearchAfterAndBulkCreateParams { - gap: moment.Duration | null; - previousStartedAt: Date | null | undefined; - ruleParams: RuleTypeParams; - services: AlertServices; - listClient: ListClient; - exceptionsList: ExceptionListItemSchema[]; - logger: Logger; - id: string; - inputIndexPattern: string[]; - signalsIndex: string; - name: string; - actions: RuleAlertAction[]; - createdAt: string; - createdBy: string; - updatedBy: string; - updatedAt: string; - interval: string; - enabled: boolean; - pageSize: number; - filter: unknown; - refresh: RefreshTypes; - tags: string[]; - throttle: string; - buildRuleMessage: BuildRuleMessage; -} - -export interface SearchAfterAndBulkCreateReturnType { - success: boolean; - searchAfterTimes: string[]; - bulkCreateTimes: string[]; - lastLookBackDate: Date | null | undefined; - createdSignalsCount: number; - errors: string[]; -} +import { + createSearchAfterReturnType, + createSearchAfterReturnTypeFromResponse, + createTotalHitsFromSearchResult, + getSignalTimeTuples, + mergeReturns, +} from './utils'; +import { SearchAfterAndBulkCreateParams, SearchAfterAndBulkCreateReturnType } from './types'; // search_after through documents and re-index using bulk endpoint. export const searchAfterAndBulkCreate = async ({ @@ -81,14 +43,7 @@ export const searchAfterAndBulkCreate = async ({ throttle, buildRuleMessage, }: SearchAfterAndBulkCreateParams): Promise => { - const toReturn: SearchAfterAndBulkCreateReturnType = { - success: true, - searchAfterTimes: [], - bulkCreateTimes: [], - lastLookBackDate: null, - createdSignalsCount: 0, - errors: [], - }; + let toReturn = createSearchAfterReturnType(); // sortId tells us where to start our next consecutive search_after query let sortId: string | undefined; @@ -108,13 +63,15 @@ export const searchAfterAndBulkCreate = async ({ buildRuleMessage, }); logger.debug(buildRuleMessage(`totalToFromTuples: ${totalToFromTuples.length}`)); + while (totalToFromTuples.length > 0) { const tuple = totalToFromTuples.pop(); if (tuple == null || tuple.to == null || tuple.from == null) { logger.error(buildRuleMessage(`[-] malformed date tuple`)); - toReturn.success = false; - toReturn.errors = [...new Set([...toReturn.errors, 'malformed date tuple'])]; - return toReturn; + return createSearchAfterReturnType({ + success: false, + errors: ['malformed date tuple'], + }); } signalsCreatedCount = 0; while (signalsCreatedCount < tuple.maxSignals) { @@ -122,29 +79,27 @@ export const searchAfterAndBulkCreate = async ({ logger.debug(buildRuleMessage(`sortIds: ${sortId}`)); // perform search_after with optionally undefined sortId - const { - searchResult, - searchDuration, - }: { searchResult: SignalSearchResponse; searchDuration: string } = await singleSearchAfter( - { - searchAfterSortId: sortId, - index: inputIndexPattern, - from: tuple.from.toISOString(), - to: tuple.to.toISOString(), - services, - logger, - filter, - pageSize: tuple.maxSignals < pageSize ? Math.ceil(tuple.maxSignals) : pageSize, // maximum number of docs to receive per search result. - timestampOverride: ruleParams.timestampOverride, - } - ); - toReturn.searchAfterTimes.push(searchDuration); - + const { searchResult, searchDuration, searchErrors } = await singleSearchAfter({ + searchAfterSortId: sortId, + index: inputIndexPattern, + from: tuple.from.toISOString(), + to: tuple.to.toISOString(), + services, + logger, + filter, + pageSize: tuple.maxSignals < pageSize ? Math.ceil(tuple.maxSignals) : pageSize, // maximum number of docs to receive per search result. + timestampOverride: ruleParams.timestampOverride, + }); + toReturn = mergeReturns([ + toReturn, + createSearchAfterReturnTypeFromResponse({ searchResult }), + createSearchAfterReturnType({ + searchAfterTimes: [searchDuration], + errors: searchErrors, + }), + ]); // determine if there are any candidate signals to be processed - const totalHits = - typeof searchResult.hits.total === 'number' - ? searchResult.hits.total - : searchResult.hits.total.value; + const totalHits = createTotalHitsFromSearchResult({ searchResult }); logger.debug(buildRuleMessage(`totalHits: ${totalHits}`)); logger.debug( buildRuleMessage(`searchResult.hit.hits.length: ${searchResult.hits.hits.length}`) @@ -168,17 +123,11 @@ export const searchAfterAndBulkCreate = async ({ ); break; } - toReturn.lastLookBackDate = - searchResult.hits.hits.length > 0 - ? new Date( - searchResult.hits.hits[searchResult.hits.hits.length - 1]?._source['@timestamp'] - ) - : null; // filter out the search results that match with the values found in the list. // the resulting set are signals to be indexed, given they are not duplicates // of signals already present in the signals index. - const filteredEvents: SignalSearchResponse = await filterEventsAgainstList({ + const filteredEvents = await filterEventsAgainstList({ listClient, exceptionsList, logger, @@ -222,19 +171,21 @@ export const searchAfterAndBulkCreate = async ({ tags, throttle, }); - logger.debug(buildRuleMessage(`created ${createdCount} signals`)); - toReturn.createdSignalsCount += createdCount; + toReturn = mergeReturns([ + toReturn, + createSearchAfterReturnType({ + success: bulkSuccess, + createdSignalsCount: createdCount, + bulkCreateTimes: bulkDuration ? [bulkDuration] : undefined, + errors: bulkErrors, + }), + ]); signalsCreatedCount += createdCount; + logger.debug(buildRuleMessage(`created ${createdCount} signals`)); logger.debug(buildRuleMessage(`signalsCreatedCount: ${signalsCreatedCount}`)); - if (bulkDuration) { - toReturn.bulkCreateTimes.push(bulkDuration); - } - logger.debug( buildRuleMessage(`filteredEvents.hits.hits: ${filteredEvents.hits.hits.length}`) ); - toReturn.success = toReturn.success && bulkSuccess; - toReturn.errors = [...new Set([...toReturn.errors, ...bulkErrors])]; } // we are guaranteed to have searchResult hits at this point @@ -249,9 +200,13 @@ export const searchAfterAndBulkCreate = async ({ } } catch (exc: unknown) { logger.error(buildRuleMessage(`[-] search_after and bulk threw an error ${exc}`)); - toReturn.success = false; - toReturn.errors = [...new Set([...toReturn.errors, `${exc}`])]; - return toReturn; + return mergeReturns([ + toReturn, + createSearchAfterReturnType({ + success: false, + errors: [`${exc}`], + }), + ]); } } } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts index 3ff5d5d2a6e13..382acf2f38245 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts @@ -18,11 +18,8 @@ import { sortExceptionItems, } from './utils'; import { parseScheduleDates } from '../../../../common/detection_engine/parse_schedule_dates'; -import { RuleExecutorOptions } from './types'; -import { - searchAfterAndBulkCreate, - SearchAfterAndBulkCreateReturnType, -} from './search_after_bulk_create'; +import { RuleExecutorOptions, SearchAfterAndBulkCreateReturnType } from './types'; +import { searchAfterAndBulkCreate } from './search_after_bulk_create'; import { scheduleNotificationActions } from '../notifications/schedule_notification_actions'; import { RuleAlertType } from '../rules/types'; import { findMlSignals } from './find_ml_signals'; @@ -36,7 +33,17 @@ jest.mock('./rule_status_saved_objects_client'); jest.mock('./rule_status_service'); jest.mock('./search_after_bulk_create'); jest.mock('./get_filter'); -jest.mock('./utils'); +jest.mock('./utils', () => { + const original = jest.requireActual('./utils'); + return { + ...original, + getGapBetweenRuns: jest.fn(), + getGapMaxCatchupRatio: jest.fn(), + getListsClient: jest.fn(), + getExceptions: jest.fn(), + sortExceptionItems: jest.fn(), + }; +}); jest.mock('../notifications/schedule_notification_actions'); jest.mock('./find_ml_signals'); jest.mock('./bulk_create_ml_signals'); @@ -383,6 +390,7 @@ describe('rules_notification_alert_type', () => { }, ]); (findMlSignals as jest.Mock).mockResolvedValue({ + _shards: {}, hits: { hits: [], }, @@ -401,6 +409,7 @@ describe('rules_notification_alert_type', () => { payload = getPayload(ruleAlert, alertServices) as jest.Mocked; jobsSummaryMock.mockResolvedValue([]); (findMlSignals as jest.Mock).mockResolvedValue({ + _shards: {}, hits: { hits: [], }, @@ -409,6 +418,7 @@ describe('rules_notification_alert_type', () => { success: true, bulkCreateDuration: 0, createdItemsCount: 0, + errors: [], }); await alert.executor(payload); expect(ruleStatusService.success).not.toHaveBeenCalled(); @@ -425,6 +435,7 @@ describe('rules_notification_alert_type', () => { }, ]); (findMlSignals as jest.Mock).mockResolvedValue({ + _shards: { failed: 0 }, hits: { hits: [{}], }, @@ -433,6 +444,7 @@ describe('rules_notification_alert_type', () => { success: true, bulkCreateDuration: 1, createdItemsCount: 1, + errors: [], }); await alert.executor(payload); expect(ruleStatusService.success).toHaveBeenCalled(); @@ -460,6 +472,7 @@ describe('rules_notification_alert_type', () => { }); jobsSummaryMock.mockResolvedValue([]); (findMlSignals as jest.Mock).mockResolvedValue({ + _shards: { failed: 0 }, hits: { hits: [{}], }, @@ -468,6 +481,7 @@ describe('rules_notification_alert_type', () => { success: true, bulkCreateDuration: 1, createdItemsCount: 1, + errors: [], }); await alert.executor(payload); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 196c17b42221b..97ab12f905358 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -22,10 +22,7 @@ import { import { parseScheduleDates } from '../../../../common/detection_engine/parse_schedule_dates'; import { SetupPlugins } from '../../../plugin'; import { getInputIndex } from './get_input_output_index'; -import { - searchAfterAndBulkCreate, - SearchAfterAndBulkCreateReturnType, -} from './search_after_bulk_create'; +import { searchAfterAndBulkCreate } from './search_after_bulk_create'; import { getFilter } from './get_filter'; import { SignalRuleAlertTypeDefinition, RuleAlertAttributes } from './types'; import { @@ -34,6 +31,10 @@ import { getExceptions, getGapMaxCatchupRatio, MAX_RULE_GAP_RATIO, + createErrorsFromShard, + createSearchAfterReturnType, + mergeReturns, + createSearchAfterReturnTypeFromResponse, } from './utils'; import { signalParamsSchema } from './signal_params_schema'; import { siemRuleActionGroups } from './siem_rule_action_groups'; @@ -104,14 +105,7 @@ export const signalRulesAlertType = ({ } = params; const searchAfterSize = Math.min(maxSignals, DEFAULT_SEARCH_AFTER_PAGE_SIZE); let hasError: boolean = false; - let result: SearchAfterAndBulkCreateReturnType = { - success: false, - bulkCreateTimes: [], - searchAfterTimes: [], - lastLookBackDate: null, - createdSignalsCount: 0, - errors: [], - }; + let result = createSearchAfterReturnType(); const ruleStatusClient = ruleStatusSavedObjectsClientFactory(services.savedObjectsClient); const ruleStatusService = await ruleStatusServiceFactory({ alertId, @@ -255,12 +249,22 @@ export const signalRulesAlertType = ({ refresh, tags, }); - result.success = success; - result.errors = errors; - result.createdSignalsCount = createdItemsCount; - if (bulkCreateDuration) { - result.bulkCreateTimes.push(bulkCreateDuration); - } + // The legacy ES client does not define failures when it can be present on the structure, hence why I have the & { failures: [] } + const shardFailures = + (anomalyResults._shards as typeof anomalyResults._shards & { failures: [] }).failures ?? + []; + const searchErrors = createErrorsFromShard({ + errors: shardFailures, + }); + result = mergeReturns([ + result, + createSearchAfterReturnType({ + success: success && anomalyResults._shards.failed === 0, + errors: [...errors, ...searchErrors], + createdSignalsCount: createdItemsCount, + bulkCreateTimes: bulkCreateDuration ? [bulkCreateDuration] : [], + }), + ]); } else if (isEqlRule(type)) { throw new Error('EQL Rules are under development, execution is not yet implemented'); } else if (isThresholdRule(type) && threshold) { @@ -276,7 +280,7 @@ export const signalRulesAlertType = ({ lists: exceptionItems ?? [], }); - const { searchResult: thresholdResults } = await findThresholdSignals({ + const { searchResult: thresholdResults, searchErrors } = await findThresholdSignals({ inputIndexPattern: inputIndex, from, to, @@ -313,12 +317,16 @@ export const signalRulesAlertType = ({ refresh, tags, }); - result.success = success; - result.errors = errors; - result.createdSignalsCount = createdItemsCount; - if (bulkCreateDuration) { - result.bulkCreateTimes.push(bulkCreateDuration); - } + result = mergeReturns([ + result, + createSearchAfterReturnTypeFromResponse({ searchResult: thresholdResults }), + createSearchAfterReturnType({ + success, + errors: [...errors, ...searchErrors], + createdSignalsCount: createdItemsCount, + bulkCreateTimes: bulkCreateDuration ? [bulkCreateDuration] : [], + }), + ]); } else if (isThreatMatchRule(type)) { if ( threatQuery == null || diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.test.ts index 250b891eb1f2c..da81911f07ad9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.test.ts @@ -11,6 +11,7 @@ import { } from './__mocks__/es_results'; import { singleSearchAfter } from './single_search_after'; import { alertsMock, AlertServicesMock } from '../../../../../alerts/server/mocks'; +import { ShardError } from '../../types'; describe('singleSearchAfter', () => { const mockService: AlertServicesMock = alertsMock.createAlertServices(); @@ -20,10 +21,71 @@ describe('singleSearchAfter', () => { }); test('if singleSearchAfter works without a given sort id', async () => { - let searchAfterSortId; - mockService.callCluster.mockResolvedValue(sampleDocSearchResultsNoSortId); + mockService.callCluster.mockResolvedValue(sampleDocSearchResultsNoSortId()); const { searchResult } = await singleSearchAfter({ - searchAfterSortId, + searchAfterSortId: undefined, + index: [], + from: 'now-360s', + to: 'now', + services: mockService, + logger: mockLogger, + pageSize: 1, + filter: undefined, + timestampOverride: undefined, + }); + expect(searchResult).toEqual(sampleDocSearchResultsNoSortId()); + }); + test('if singleSearchAfter returns an empty failure array', async () => { + mockService.callCluster.mockResolvedValue(sampleDocSearchResultsNoSortId()); + const { searchErrors } = await singleSearchAfter({ + searchAfterSortId: undefined, + index: [], + from: 'now-360s', + to: 'now', + services: mockService, + logger: mockLogger, + pageSize: 1, + filter: undefined, + timestampOverride: undefined, + }); + expect(searchErrors).toEqual([]); + }); + test('if singleSearchAfter will return an error array', async () => { + const errors: ShardError[] = [ + { + shard: 1, + index: 'index-123', + node: 'node-123', + reason: { + type: 'some type', + reason: 'some reason', + index_uuid: 'uuid-123', + index: 'index-123', + caused_by: { + type: 'some type', + reason: 'some reason', + }, + }, + }, + ]; + mockService.callCluster.mockResolvedValue({ + took: 10, + timed_out: false, + _shards: { + total: 10, + successful: 10, + failed: 1, + skipped: 0, + failures: errors, + }, + hits: { + total: 100, + max_score: 100, + hits: [], + }, + }); + const { searchErrors } = await singleSearchAfter({ + searchAfterSortId: undefined, index: [], from: 'now-360s', to: 'now', @@ -33,11 +95,11 @@ describe('singleSearchAfter', () => { filter: undefined, timestampOverride: undefined, }); - expect(searchResult).toEqual(sampleDocSearchResultsNoSortId); + expect(searchErrors).toEqual(['reason: some reason, type: some type, caused by: some reason']); }); test('if singleSearchAfter works with a given sort id', async () => { const searchAfterSortId = '1234567891111'; - mockService.callCluster.mockResolvedValue(sampleDocSearchResultsWithSortId); + mockService.callCluster.mockResolvedValue(sampleDocSearchResultsWithSortId()); const { searchResult } = await singleSearchAfter({ searchAfterSortId, index: [], @@ -49,7 +111,7 @@ describe('singleSearchAfter', () => { filter: undefined, timestampOverride: undefined, }); - expect(searchResult).toEqual(sampleDocSearchResultsWithSortId); + expect(searchResult).toEqual(sampleDocSearchResultsWithSortId()); }); test('if singleSearchAfter throws error', async () => { const searchAfterSortId = '1234567891111'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts index 92ce7a2836115..f758adb21611c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts @@ -9,7 +9,7 @@ import { AlertServices } from '../../../../../alerts/server'; import { Logger } from '../../../../../../../src/core/server'; import { SignalSearchResponse } from './types'; import { buildEventsSearchQuery } from './build_events_query'; -import { makeFloatString } from './utils'; +import { createErrorsFromShard, makeFloatString } from './utils'; import { TimestampOverrideOrUndefined } from '../../../../common/detection_engine/schemas/common/schemas'; interface SingleSearchAfterParams { @@ -40,6 +40,7 @@ export const singleSearchAfter = async ({ }: SingleSearchAfterParams): Promise<{ searchResult: SignalSearchResponse; searchDuration: string; + searchErrors: string[]; }> => { try { const searchAfterQuery = buildEventsSearchQuery({ @@ -59,7 +60,14 @@ export const singleSearchAfter = async ({ searchAfterQuery ); const end = performance.now(); - return { searchResult: nextSearchAfterResult, searchDuration: makeFloatString(end - start) }; + const searchErrors = createErrorsFromShard({ + errors: nextSearchAfterResult._shards.failures ?? [], + }); + return { + searchResult: nextSearchAfterResult, + searchDuration: makeFloatString(end - start), + searchErrors, + }; } catch (exc) { logger.error(`[-] nextSearchAfter threw an error ${exc}`); throw exc; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts index 7542128d83769..a6d4a2ba58ddd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts @@ -9,12 +9,10 @@ import { getThreatList } from './get_threat_list'; import { buildThreatMappingFilter } from './build_threat_mapping_filter'; import { getFilter } from '../get_filter'; -import { - searchAfterAndBulkCreate, - SearchAfterAndBulkCreateReturnType, -} from '../search_after_bulk_create'; +import { searchAfterAndBulkCreate } from '../search_after_bulk_create'; import { CreateThreatSignalOptions, ThreatListItem } from './types'; import { combineResults } from './utils'; +import { SearchAfterAndBulkCreateReturnType } from '../types'; export const createThreatSignal = async ({ threatMapping, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts index 9027475d71c4a..f416ae6703b66 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts @@ -6,9 +6,9 @@ import { getThreatList } from './get_threat_list'; -import { SearchAfterAndBulkCreateReturnType } from '../search_after_bulk_create'; import { CreateThreatSignalsOptions } from './types'; import { createThreatSignal } from './create_threat_signal'; +import { SearchAfterAndBulkCreateReturnType } from '../types'; export const createThreatSignals = async ({ threatMapping, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts index 4c3cd9943adb4..d63f2d2b3b6aa 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts @@ -19,10 +19,10 @@ import { import { PartialFilter, RuleTypeParams } from '../../types'; import { AlertServices } from '../../../../../../alerts/server'; import { ExceptionListItemSchema } from '../../../../../../lists/common/schemas'; -import { SearchAfterAndBulkCreateReturnType } from '../search_after_bulk_create'; import { ILegacyScopedClusterClient, Logger } from '../../../../../../../../src/core/server'; import { RuleAlertAction } from '../../../../../common/detection_engine/types'; import { BuildRuleMessage } from '../rule_messages'; +import { SearchAfterAndBulkCreateReturnType } from '../types'; export interface CreateThreatSignalsOptions { threatMapping: ThreatMapping; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts index 48bdf430b940e..27593b40b0c8f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchAfterAndBulkCreateReturnType } from '../search_after_bulk_create'; +import { SearchAfterAndBulkCreateReturnType } from '../types'; import { calculateAdditiveMax, combineResults } from './utils'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts index 38bbb70b6c4ec..401a4a1acb065 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchAfterAndBulkCreateReturnType } from '../search_after_bulk_create'; +import { SearchAfterAndBulkCreateReturnType } from '../types'; /** * Given two timers this will take the max of each and add them to each other and return that addition. diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts index 23aa786558a99..6ebdca0764e9d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts @@ -5,12 +5,22 @@ */ import { DslQuery, Filter } from 'src/plugins/data/common'; +import moment from 'moment'; import { Status } from '../../../../common/detection_engine/schemas/common/schemas'; import { RulesSchema } from '../../../../common/detection_engine/schemas/response/rules_schema'; -import { AlertType, AlertTypeState, AlertExecutorOptions } from '../../../../../alerts/server'; +import { + AlertType, + AlertTypeState, + AlertExecutorOptions, + AlertServices, +} from '../../../../../alerts/server'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; -import { RuleTypeParams } from '../types'; +import { RuleTypeParams, RefreshTypes } from '../types'; import { SearchResponse } from '../../types'; +import { ListClient } from '../../../../../lists/server'; +import { Logger } from '../../../../../../../src/core/server'; +import { ExceptionListItemSchema } from '../../../../../lists/common/schemas'; +import { BuildRuleMessage } from './rule_messages'; // used for gap detection code // eslint-disable-next-line @typescript-eslint/naming-convention @@ -179,3 +189,39 @@ export interface QueryFilter { must_not: Filter[]; }; } + +export interface SearchAfterAndBulkCreateParams { + gap: moment.Duration | null; + previousStartedAt: Date | null | undefined; + ruleParams: RuleTypeParams; + services: AlertServices; + listClient: ListClient; + exceptionsList: ExceptionListItemSchema[]; + logger: Logger; + id: string; + inputIndexPattern: string[]; + signalsIndex: string; + name: string; + actions: RuleAlertAction[]; + createdAt: string; + createdBy: string; + updatedBy: string; + updatedAt: string; + interval: string; + enabled: boolean; + pageSize: number; + filter: unknown; + refresh: RefreshTypes; + tags: string[]; + throttle: string; + buildRuleMessage: BuildRuleMessage; +} + +export interface SearchAfterAndBulkCreateReturnType { + success: boolean; + searchAfterTimes: string[]; + bulkCreateTimes: string[]; + lastLookBackDate: Date | null | undefined; + createdSignalsCount: number; + errors: string[]; +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts index 123b9c9bdffa2..97f3dbeaf4489 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts @@ -25,15 +25,25 @@ import { getListsClient, getSignalTimeTuples, getExceptions, + createErrorsFromShard, + createSearchAfterReturnTypeFromResponse, + createSearchAfterReturnType, + mergeReturns, + createTotalHitsFromSearchResult, } from './utils'; -import { BulkResponseErrorAggregation } from './types'; +import { BulkResponseErrorAggregation, SearchAfterAndBulkCreateReturnType } from './types'; import { sampleBulkResponse, sampleEmptyBulkResponse, sampleBulkError, sampleBulkErrorItem, mockLogger, + sampleDocSearchResultsWithSortId, + sampleEmptyDocSearchResults, + sampleDocSearchResultsNoSortIdNoHits, + repeatedSearchResultsWithSortId, } from './__mocks__/es_results'; +import { ShardError } from '../../types'; const buildRuleMessage = buildRuleMessageFactory({ id: 'fake id', @@ -783,4 +793,278 @@ describe('utils', () => { expect(exceptions).toEqual([]); }); }); + + describe('createErrorsFromShard', () => { + test('empty errors will return an empty array', () => { + const createdErrors = createErrorsFromShard({ errors: [] }); + expect(createdErrors).toEqual([]); + }); + + test('single error will return single converted array of a string of a reason', () => { + const errors: ShardError[] = [ + { + shard: 1, + index: 'index-123', + node: 'node-123', + reason: { + type: 'some type', + reason: 'some reason', + index_uuid: 'uuid-123', + index: 'index-123', + caused_by: { + type: 'some type', + reason: 'some reason', + }, + }, + }, + ]; + const createdErrors = createErrorsFromShard({ errors }); + expect(createdErrors).toEqual([ + 'reason: some reason, type: some type, caused by: some reason', + ]); + }); + + test('two errors will return two converted arrays to a string of a reason', () => { + const errors: ShardError[] = [ + { + shard: 1, + index: 'index-123', + node: 'node-123', + reason: { + type: 'some type', + reason: 'some reason', + index_uuid: 'uuid-123', + index: 'index-123', + caused_by: { + type: 'some type', + reason: 'some reason', + }, + }, + }, + { + shard: 2, + index: 'index-345', + node: 'node-345', + reason: { + type: 'some type 2', + reason: 'some reason 2', + index_uuid: 'uuid-345', + index: 'index-345', + caused_by: { + type: 'some type 2', + reason: 'some reason 2', + }, + }, + }, + ]; + const createdErrors = createErrorsFromShard({ errors }); + expect(createdErrors).toEqual([ + 'reason: some reason, type: some type, caused by: some reason', + 'reason: some reason 2, type: some type 2, caused by: some reason 2', + ]); + }); + }); + + describe('createSearchAfterReturnTypeFromResponse', () => { + test('empty results will return successful type', () => { + const searchResult = sampleEmptyDocSearchResults(); + const newSearchResult = createSearchAfterReturnTypeFromResponse({ searchResult }); + const expected: SearchAfterAndBulkCreateReturnType = { + bulkCreateTimes: [], + createdSignalsCount: 0, + errors: [], + lastLookBackDate: null, + searchAfterTimes: [], + success: true, + }; + expect(newSearchResult).toEqual(expected); + }); + + test('multiple results will return successful type with expected success', () => { + const searchResult = sampleDocSearchResultsWithSortId(); + const newSearchResult = createSearchAfterReturnTypeFromResponse({ searchResult }); + const expected: SearchAfterAndBulkCreateReturnType = { + bulkCreateTimes: [], + createdSignalsCount: 0, + errors: [], + lastLookBackDate: new Date('2020-04-20T21:27:45.000Z'), + searchAfterTimes: [], + success: true, + }; + expect(newSearchResult).toEqual(expected); + }); + + test('result with error will create success: false within the result set', () => { + const searchResult = sampleDocSearchResultsNoSortIdNoHits(); + searchResult._shards.failed = 1; + const { success } = createSearchAfterReturnTypeFromResponse({ searchResult }); + expect(success).toEqual(false); + }); + + test('result with error will create success: false within the result set if failed is 2 or more', () => { + const searchResult = sampleDocSearchResultsNoSortIdNoHits(); + searchResult._shards.failed = 2; + const { success } = createSearchAfterReturnTypeFromResponse({ searchResult }); + expect(success).toEqual(false); + }); + + test('result with error will create success: true within the result set if failed is 0', () => { + const searchResult = sampleDocSearchResultsNoSortIdNoHits(); + searchResult._shards.failed = 0; + const { success } = createSearchAfterReturnTypeFromResponse({ searchResult }); + expect(success).toEqual(true); + }); + }); + + describe('createSearchAfterReturnType', () => { + test('createSearchAfterReturnType will return full object when nothing is passed', () => { + const searchAfterReturnType = createSearchAfterReturnType(); + const expected: SearchAfterAndBulkCreateReturnType = { + bulkCreateTimes: [], + createdSignalsCount: 0, + errors: [], + lastLookBackDate: null, + searchAfterTimes: [], + success: true, + }; + expect(searchAfterReturnType).toEqual(expected); + }); + + test('createSearchAfterReturnType can override all values', () => { + const searchAfterReturnType = createSearchAfterReturnType({ + bulkCreateTimes: ['123'], + createdSignalsCount: 5, + errors: ['error 1'], + lastLookBackDate: new Date('2020-09-21T18:51:25.193Z'), + searchAfterTimes: ['123'], + success: false, + }); + const expected: SearchAfterAndBulkCreateReturnType = { + bulkCreateTimes: ['123'], + createdSignalsCount: 5, + errors: ['error 1'], + lastLookBackDate: new Date('2020-09-21T18:51:25.193Z'), + searchAfterTimes: ['123'], + success: false, + }; + expect(searchAfterReturnType).toEqual(expected); + }); + + test('createSearchAfterReturnType can override select values', () => { + const searchAfterReturnType = createSearchAfterReturnType({ + createdSignalsCount: 5, + errors: ['error 1'], + }); + const expected: SearchAfterAndBulkCreateReturnType = { + bulkCreateTimes: [], + createdSignalsCount: 5, + errors: ['error 1'], + lastLookBackDate: null, + searchAfterTimes: [], + success: true, + }; + expect(searchAfterReturnType).toEqual(expected); + }); + }); + + describe('mergeReturns', () => { + test('it merges a default "prev" and "next" correctly ', () => { + const merged = mergeReturns([createSearchAfterReturnType(), createSearchAfterReturnType()]); + const expected: SearchAfterAndBulkCreateReturnType = { + bulkCreateTimes: [], + createdSignalsCount: 0, + errors: [], + lastLookBackDate: null, + searchAfterTimes: [], + success: true, + }; + expect(merged).toEqual(expected); + }); + + test('it merges search in with two default search results where "prev" "success" is false correctly', () => { + const { success } = mergeReturns([ + createSearchAfterReturnType({ success: false }), + createSearchAfterReturnType(), + ]); + expect(success).toEqual(false); + }); + + test('it merges search in with two default search results where "next" "success" is false correctly', () => { + const { success } = mergeReturns([ + createSearchAfterReturnType(), + createSearchAfterReturnType({ success: false }), + ]); + expect(success).toEqual(false); + }); + + test('it merges search where the lastLookBackDate is the "next" date when given', () => { + const { lastLookBackDate } = mergeReturns([ + createSearchAfterReturnType({ + lastLookBackDate: new Date('2020-08-21T19:21:46.194Z'), + }), + createSearchAfterReturnType({ + lastLookBackDate: new Date('2020-09-21T19:21:46.194Z'), + }), + ]); + expect(lastLookBackDate).toEqual(new Date('2020-09-21T19:21:46.194Z')); + }); + + test('it merges search where the lastLookBackDate is the "prev" if given undefined for "next', () => { + const { lastLookBackDate } = mergeReturns([ + createSearchAfterReturnType({ + lastLookBackDate: new Date('2020-08-21T19:21:46.194Z'), + }), + createSearchAfterReturnType({ + lastLookBackDate: undefined, + }), + ]); + expect(lastLookBackDate).toEqual(new Date('2020-08-21T19:21:46.194Z')); + }); + + test('it merges search where values from "next" and "prev" are computed together', () => { + const merged = mergeReturns([ + createSearchAfterReturnType({ + bulkCreateTimes: ['123'], + createdSignalsCount: 3, + errors: ['error 1', 'error 2'], + lastLookBackDate: new Date('2020-08-21T18:51:25.193Z'), + searchAfterTimes: ['123'], + success: true, + }), + createSearchAfterReturnType({ + bulkCreateTimes: ['456'], + createdSignalsCount: 2, + errors: ['error 3'], + lastLookBackDate: new Date('2020-09-21T18:51:25.193Z'), + searchAfterTimes: ['567'], + success: true, + }), + ]); + const expected: SearchAfterAndBulkCreateReturnType = { + bulkCreateTimes: ['123', '456'], // concatenates the prev and next together + createdSignalsCount: 5, // Adds the 3 and 2 together + errors: ['error 1', 'error 2', 'error 3'], // concatenates the prev and next together + lastLookBackDate: new Date('2020-09-21T18:51:25.193Z'), // takes the next lastLookBackDate + searchAfterTimes: ['123', '567'], // concatenates the searchAfterTimes together + success: true, // Defaults to success true is all of it was successful + }; + expect(merged).toEqual(expected); + }); + }); + + describe('createTotalHitsFromSearchResult', () => { + test('it should return 0 for empty results', () => { + const result = createTotalHitsFromSearchResult({ + searchResult: sampleEmptyDocSearchResults(), + }); + expect(result).toEqual(0); + }); + + test('it should return 4 for 4 result sets', () => { + const result = createTotalHitsFromSearchResult({ + searchResult: repeatedSearchResultsWithSortId(4, 1, ['1', '2', '3', '4']), + }); + expect(result).toEqual(4); + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts index 9f1e5d6980466..2eabc03dccad7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts @@ -12,11 +12,18 @@ import { AlertServices, parseDuration } from '../../../../../alerts/server'; import { ExceptionListClient, ListClient, ListPluginSetup } from '../../../../../lists/server'; import { ExceptionListItemSchema } from '../../../../../lists/common/schemas'; import { ListArray } from '../../../../common/detection_engine/schemas/types/lists'; -import { BulkResponse, BulkResponseErrorAggregation, isValidUnit } from './types'; +import { + BulkResponse, + BulkResponseErrorAggregation, + isValidUnit, + SearchAfterAndBulkCreateReturnType, + SignalSearchResponse, +} from './types'; import { BuildRuleMessage } from './rule_messages'; import { parseScheduleDates } from '../../../../common/detection_engine/parse_schedule_dates'; import { hasLargeValueList } from '../../../../common/detection_engine/utils'; import { MAX_EXCEPTION_LIST_SIZE } from '../../../../../lists/common/constants'; +import { ShardError } from '../../types'; interface SortExceptionsReturn { exceptionsWithValueLists: ExceptionListItemSchema[]; @@ -439,3 +446,97 @@ export const getSignalTimeTuples = ({ ); return totalToFromTuples; }; + +/** + * Given errors from a search query this will return an array of strings derived from the errors. + * @param errors The errors to derive the strings from + */ +export const createErrorsFromShard = ({ errors }: { errors: ShardError[] }): string[] => { + return errors.map((error) => { + return `reason: ${error.reason.reason}, type: ${error.reason.caused_by.type}, caused by: ${error.reason.caused_by.reason}`; + }); +}; + +export const createSearchAfterReturnTypeFromResponse = ({ + searchResult, +}: { + searchResult: SignalSearchResponse; +}): SearchAfterAndBulkCreateReturnType => { + return createSearchAfterReturnType({ + success: searchResult._shards.failed === 0, + lastLookBackDate: + searchResult.hits.hits.length > 0 + ? new Date(searchResult.hits.hits[searchResult.hits.hits.length - 1]?._source['@timestamp']) + : undefined, + }); +}; + +export const createSearchAfterReturnType = ({ + success, + searchAfterTimes, + bulkCreateTimes, + lastLookBackDate, + createdSignalsCount, + errors, +}: { + success?: boolean | undefined; + searchAfterTimes?: string[] | undefined; + bulkCreateTimes?: string[] | undefined; + lastLookBackDate?: Date | undefined; + createdSignalsCount?: number | undefined; + errors?: string[] | undefined; +} = {}): SearchAfterAndBulkCreateReturnType => { + return { + success: success ?? true, + searchAfterTimes: searchAfterTimes ?? [], + bulkCreateTimes: bulkCreateTimes ?? [], + lastLookBackDate: lastLookBackDate ?? null, + createdSignalsCount: createdSignalsCount ?? 0, + errors: errors ?? [], + }; +}; + +export const mergeReturns = ( + searchAfters: SearchAfterAndBulkCreateReturnType[] +): SearchAfterAndBulkCreateReturnType => { + return searchAfters.reduce((prev, next) => { + const { + success: existingSuccess, + searchAfterTimes: existingSearchAfterTimes, + bulkCreateTimes: existingBulkCreateTimes, + lastLookBackDate: existingLastLookBackDate, + createdSignalsCount: existingCreatedSignalsCount, + errors: existingErrors, + } = prev; + + const { + success: newSuccess, + searchAfterTimes: newSearchAfterTimes, + bulkCreateTimes: newBulkCreateTimes, + lastLookBackDate: newLastLookBackDate, + createdSignalsCount: newCreatedSignalsCount, + errors: newErrors, + } = next; + + return { + success: existingSuccess && newSuccess, + searchAfterTimes: [...existingSearchAfterTimes, ...newSearchAfterTimes], + bulkCreateTimes: [...existingBulkCreateTimes, ...newBulkCreateTimes], + lastLookBackDate: newLastLookBackDate ?? existingLastLookBackDate, + createdSignalsCount: existingCreatedSignalsCount + newCreatedSignalsCount, + errors: [...new Set([...existingErrors, ...newErrors])], + }; + }); +}; + +export const createTotalHitsFromSearchResult = ({ + searchResult, +}: { + searchResult: SignalSearchResponse; +}): number => { + const totalHits = + typeof searchResult.hits.total === 'number' + ? searchResult.hits.total + : searchResult.hits.total.value; + return totalHits; +}; diff --git a/x-pack/plugins/security_solution/server/lib/events/elasticsearch_adapter.ts b/x-pack/plugins/security_solution/server/lib/events/elasticsearch_adapter.ts index dda52e26ca42b..8b656272ecc99 100644 --- a/x-pack/plugins/security_solution/server/lib/events/elasticsearch_adapter.ts +++ b/x-pack/plugins/security_solution/server/lib/events/elasticsearch_adapter.ts @@ -26,7 +26,6 @@ import { TimelineDetailsData, TimelineEdges, } from '../../graphql/types'; -import { baseCategoryFields } from '../../utils/beat_schema/8.0.0'; import { reduceFields } from '../../utils/build_query/reduce_fields'; import { mergeFieldsWithHit, inspectStringifyObject } from '../../utils/build_query'; import { eventFieldsMap } from '../ecs_fields'; @@ -44,6 +43,8 @@ import { TimelineRequestOptions, } from './types'; +const baseCategoryFields = ['@timestamp', 'labels', 'message', 'tags']; + export class ElasticsearchEventsAdapter implements EventsAdapter { constructor(private readonly framework: FrameworkAdapter) {} diff --git a/x-pack/plugins/security_solution/server/lib/index_fields/elasticsearch_adapter.ts b/x-pack/plugins/security_solution/server/lib/index_fields/elasticsearch_adapter.ts index 777b1cf3bb80d..6cfa13bfd2a7a 100644 --- a/x-pack/plugins/security_solution/server/lib/index_fields/elasticsearch_adapter.ts +++ b/x-pack/plugins/security_solution/server/lib/index_fields/elasticsearch_adapter.ts @@ -4,167 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isEmpty } from 'lodash/fp'; - -import { IndexField } from '../../graphql/types'; -import { baseCategoryFields, getDocumentation, hasDocumentation } from '../../utils/beat_schema'; -import { FrameworkAdapter, FrameworkRequest } from '../framework'; -import { FieldsAdapter, IndexFieldDescriptor } from './types'; +import { FrameworkRequest } from '../framework'; +import { FieldsAdapter } from './types'; export class ElasticsearchIndexFieldAdapter implements FieldsAdapter { - constructor(private readonly framework: FrameworkAdapter) {} - public async getIndexFields(request: FrameworkRequest, indices: string[]): Promise { - const indexPatternsService = this.framework.getIndexPatternsService(request); - const responsesIndexFields = await Promise.all( - indices.map((index) => { - return indexPatternsService.getFieldsForWildcard({ - pattern: index, - }); - }) - ); - return formatIndexFields(responsesIndexFields, indices); + // Deprecated until we delete all the code + public async getIndexFields(request: FrameworkRequest, indices: string[]): Promise { + return Promise.resolve(['deprecated']); } } - -const missingFields = [ - { - name: '_id', - type: 'string', - searchable: true, - aggregatable: false, - readFromDocValues: true, - }, - { - name: '_index', - type: 'string', - searchable: true, - aggregatable: true, - readFromDocValues: true, - }, -]; - -/** - * Creates a single field item. - * - * This is a mutatious HOT CODE PATH function that will have array sizes up to 4.7 megs - * in size at a time calling this function repeatedly. This function should be as optimized as possible - * and should avoid any and all creation of new arrays, iterating over the arrays or performing - * any n^2 operations. - * @param indexesAlias The index alias - * @param index The index its self - * @param indexesAliasIdx The index within the alias - */ -export const createFieldItem = ( - indexesAlias: string[], - index: IndexFieldDescriptor, - indexesAliasIdx: number -): IndexField => { - const alias = indexesAlias[indexesAliasIdx]; - const splitName = index.name.split('.'); - const category = baseCategoryFields.includes(splitName[0]) ? 'base' : splitName[0]; - return { - ...(hasDocumentation(alias, index.name) ? getDocumentation(alias, index.name) : {}), - ...index, - category, - indexes: [alias], - }; -}; - -/** - * This is a mutatious HOT CODE PATH function that will have array sizes up to 4.7 megs - * in size at a time when being called. This function should be as optimized as possible - * and should avoid any and all creation of new arrays, iterating over the arrays or performing - * any n^2 operations. The `.push`, and `forEach` operations are expected within this function - * to speed up performance. - * - * This intentionally waits for the next tick on the event loop to process as the large 4.7 megs - * has already consumed a lot of the event loop processing up to this function and we want to give - * I/O opportunity to occur by scheduling this on the next loop. - * @param responsesIndexFields The response index fields to loop over - * @param indexesAlias The index aliases such as filebeat-* - */ -export const formatFirstFields = async ( - responsesIndexFields: IndexFieldDescriptor[][], - indexesAlias: string[] -): Promise => { - return new Promise((resolve) => { - setTimeout(() => { - resolve( - responsesIndexFields.reduce( - ( - accumulator: IndexField[], - indexFields: IndexFieldDescriptor[], - indexesAliasIdx: number - ) => { - missingFields.forEach((index) => { - const item = createFieldItem(indexesAlias, index, indexesAliasIdx); - accumulator.push(item); - }); - indexFields.forEach((index) => { - const item = createFieldItem(indexesAlias, index, indexesAliasIdx); - accumulator.push(item); - }); - return accumulator; - }, - [] - ) - ); - }); - }); -}; - -/** - * This is a mutatious HOT CODE PATH function that will have array sizes up to 4.7 megs - * in size at a time when being called. This function should be as optimized as possible - * and should avoid any and all creation of new arrays, iterating over the arrays or performing - * any n^2 operations. The `.push`, and `forEach` operations are expected within this function - * to speed up performance. The "indexFieldNameHash" side effect hash avoids additional expensive n^2 - * look ups. - * - * This intentionally waits for the next tick on the event loop to process as the large 4.7 megs - * has already consumed a lot of the event loop processing up to this function and we want to give - * I/O opportunity to occur by scheduling this on the next loop. - * @param fields The index fields to create the secondary fields for - */ -export const formatSecondFields = async (fields: IndexField[]): Promise => { - return new Promise((resolve) => { - setTimeout(() => { - const indexFieldNameHash: Record = {}; - const reduced = fields.reduce((accumulator: IndexField[], indexfield: IndexField) => { - const alreadyExistingIndexField = indexFieldNameHash[indexfield.name]; - if (alreadyExistingIndexField != null) { - const existingIndexField = accumulator[alreadyExistingIndexField]; - if (isEmpty(accumulator[alreadyExistingIndexField].description)) { - accumulator[alreadyExistingIndexField].description = indexfield.description; - } - accumulator[alreadyExistingIndexField].indexes = Array.from( - new Set([...existingIndexField.indexes, ...indexfield.indexes]) - ); - return accumulator; - } - accumulator.push(indexfield); - indexFieldNameHash[indexfield.name] = accumulator.length - 1; - return accumulator; - }, []); - resolve(reduced); - }); - }); -}; - -/** - * Formats the index fields into a format the UI wants. - * - * NOTE: This will have array sizes up to 4.7 megs in size at a time when being called. - * This function should be as optimized as possible and should avoid any and all creation - * of new arrays, iterating over the arrays or performing any n^2 operations. - * @param responsesIndexFields The response index fields to format - * @param indexesAlias The index alias - */ -export const formatIndexFields = async ( - responsesIndexFields: IndexFieldDescriptor[][], - indexesAlias: string[] -): Promise => { - const fields = await formatFirstFields(responsesIndexFields, indexesAlias); - const secondFields = await formatSecondFields(fields); - return secondFields; -}; diff --git a/x-pack/plugins/security_solution/server/lib/index_fields/index.ts b/x-pack/plugins/security_solution/server/lib/index_fields/index.ts index a3ea8548bddc2..94966bc16a407 100644 --- a/x-pack/plugins/security_solution/server/lib/index_fields/index.ts +++ b/x-pack/plugins/security_solution/server/lib/index_fields/index.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndexField } from '../../graphql/types'; - import { FieldsAdapter } from './types'; import { FrameworkRequest } from '../framework'; export { ElasticsearchIndexFieldAdapter } from './elasticsearch_adapter'; @@ -13,7 +11,8 @@ export { ElasticsearchIndexFieldAdapter } from './elasticsearch_adapter'; export class IndexFields { constructor(private readonly adapter: FieldsAdapter) {} - public async getFields(request: FrameworkRequest, defaultIndex: string[]): Promise { + // Deprecated until we delete all the code + public async getFields(request: FrameworkRequest, defaultIndex: string[]): Promise { return this.adapter.getIndexFields(request, defaultIndex); } } diff --git a/x-pack/plugins/security_solution/server/lib/index_fields/types.ts b/x-pack/plugins/security_solution/server/lib/index_fields/types.ts index 67b3c254007e2..fdc3509d0d452 100644 --- a/x-pack/plugins/security_solution/server/lib/index_fields/types.ts +++ b/x-pack/plugins/security_solution/server/lib/index_fields/types.ts @@ -4,12 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndexField } from '../../graphql/types'; import { FrameworkRequest } from '../framework'; import { IFieldSubType } from '../../../../../../src/plugins/data/common'; export interface FieldsAdapter { - getIndexFields(req: FrameworkRequest, indices: string[]): Promise; + getIndexFields(req: FrameworkRequest, indices: string[]): Promise; } export interface IndexFieldDescriptor { diff --git a/x-pack/plugins/security_solution/server/lib/overview/elastic_adapter.test.ts b/x-pack/plugins/security_solution/server/lib/overview/elastic_adapter.test.ts deleted file mode 100644 index f421704dffe12..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/overview/elastic_adapter.test.ts +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { cloneDeep } from 'lodash/fp'; - -import { OverviewHostData, OverviewNetworkData } from '../../graphql/types'; -import { FrameworkAdapter, FrameworkRequest } from '../framework'; - -import { ElasticsearchOverviewAdapter } from './elasticsearch_adapter'; -import { - mockOptionsHost, - mockOptionsNetwork, - mockRequestHost, - mockRequestNetwork, - mockResponseHost, - mockResponseNetwork, - mockResultHost, - mockResultNetwork, - mockBuildOverviewHostQuery, - mockBuildOverviewNetworkQuery, -} from './mock'; - -jest.mock('./query.dsl', () => { - return { - buildOverviewHostQuery: jest.fn(() => mockBuildOverviewHostQuery), - buildOverviewNetworkQuery: jest.fn(() => mockBuildOverviewNetworkQuery), - }; -}); - -describe('Siem Overview elasticsearch_adapter', () => { - describe('Network Stats', () => { - describe('Happy Path - get Data', () => { - const mockCallWithRequest = jest.fn(); - mockCallWithRequest.mockResolvedValue(mockResponseNetwork); - const mockFramework: FrameworkAdapter = { - callWithRequest: mockCallWithRequest, - registerGraphQLEndpoint: jest.fn(), - getIndexPatternsService: jest.fn(), - }; - jest.doMock('../framework', () => ({ - callWithRequest: mockCallWithRequest, - })); - - test('getOverviewNetwork', async () => { - const EsOverviewNetwork = new ElasticsearchOverviewAdapter(mockFramework); - const data: OverviewNetworkData = await EsOverviewNetwork.getOverviewNetwork( - mockRequestNetwork as FrameworkRequest, - mockOptionsNetwork - ); - expect(data).toEqual(mockResultNetwork); - }); - }); - - describe('Unhappy Path - No data', () => { - const mockNoDataResponse = cloneDeep(mockResponseNetwork); - mockNoDataResponse.aggregations.unique_flow_count.doc_count = 0; - mockNoDataResponse.aggregations.unique_dns_count.doc_count = 0; - mockNoDataResponse.aggregations.unique_suricata_count.doc_count = 0; - mockNoDataResponse.aggregations.unique_zeek_count.doc_count = 0; - mockNoDataResponse.aggregations.unique_socket_count.doc_count = 0; - mockNoDataResponse.aggregations.unique_zeek_count.doc_count = 0; - mockNoDataResponse.aggregations.unique_packetbeat_count.unique_tls_count.doc_count = 0; - mockNoDataResponse.aggregations.unique_filebeat_count.unique_cisco_count.doc_count = 0; - mockNoDataResponse.aggregations.unique_filebeat_count.unique_netflow_count.doc_count = 0; - mockNoDataResponse.aggregations.unique_filebeat_count.unique_panw_count.doc_count = 0; - const mockCallWithRequest = jest.fn(); - mockCallWithRequest.mockResolvedValue(mockNoDataResponse); - const mockFramework: FrameworkAdapter = { - callWithRequest: mockCallWithRequest, - registerGraphQLEndpoint: jest.fn(), - getIndexPatternsService: jest.fn(), - }; - jest.doMock('../framework', () => ({ - callWithRequest: mockCallWithRequest, - })); - - test('getOverviewNetwork', async () => { - const EsOverviewNetwork = new ElasticsearchOverviewAdapter(mockFramework); - const data: OverviewNetworkData = await EsOverviewNetwork.getOverviewNetwork( - mockRequestNetwork as FrameworkRequest, - mockOptionsNetwork - ); - expect(data).toEqual({ - inspect: { - dsl: [JSON.stringify(mockBuildOverviewNetworkQuery, null, 2)], - response: [JSON.stringify(mockNoDataResponse, null, 2)], - }, - auditbeatSocket: 0, - filebeatCisco: 0, - filebeatNetflow: 0, - filebeatPanw: 0, - filebeatSuricata: 0, - filebeatZeek: 0, - packetbeatDNS: 0, - packetbeatFlow: 0, - packetbeatTLS: 0, - }); - }); - }); - }); - describe('Host Stats', () => { - describe('Happy Path - get Data', () => { - const mockCallWithRequest = jest.fn(); - mockCallWithRequest.mockResolvedValue(mockResponseHost); - const mockFramework: FrameworkAdapter = { - callWithRequest: mockCallWithRequest, - registerGraphQLEndpoint: jest.fn(), - getIndexPatternsService: jest.fn(), - }; - jest.doMock('../framework', () => ({ - callWithRequest: mockCallWithRequest, - })); - - test('getOverviewHost', async () => { - const EsOverviewHost = new ElasticsearchOverviewAdapter(mockFramework); - const data: OverviewHostData = await EsOverviewHost.getOverviewHost( - mockRequestHost as FrameworkRequest, - mockOptionsHost - ); - expect(data).toEqual(mockResultHost); - }); - }); - - describe('Unhappy Path - No data', () => { - const mockNoDataResponse = cloneDeep(mockResponseHost); - mockNoDataResponse.aggregations.auditd_count.doc_count = 0; - mockNoDataResponse.aggregations.endgame_module.dns_event_count.doc_count = 0; - mockNoDataResponse.aggregations.endgame_module.file_event_count.doc_count = 0; - mockNoDataResponse.aggregations.endgame_module.image_load_event_count.doc_count = 0; - mockNoDataResponse.aggregations.endgame_module.network_event_count.doc_count = 0; - mockNoDataResponse.aggregations.endgame_module.process_event_count.doc_count = 0; - mockNoDataResponse.aggregations.endgame_module.registry_event.doc_count = 0; - mockNoDataResponse.aggregations.endgame_module.security_event_count.doc_count = 0; - mockNoDataResponse.aggregations.fim_count.doc_count = 0; - mockNoDataResponse.aggregations.system_module.login_count.doc_count = 0; - mockNoDataResponse.aggregations.system_module.package_count.doc_count = 0; - mockNoDataResponse.aggregations.system_module.process_count.doc_count = 0; - mockNoDataResponse.aggregations.system_module.user_count.doc_count = 0; - mockNoDataResponse.aggregations.system_module.filebeat_count.doc_count = 0; - mockNoDataResponse.aggregations.winlog_module.security_event_count.doc_count = 0; - mockNoDataResponse.aggregations.winlog_module.mwsysmon_operational_event_count.doc_count = 0; - const mockCallWithRequest = jest.fn(); - mockCallWithRequest.mockResolvedValue(mockNoDataResponse); - const mockFramework: FrameworkAdapter = { - callWithRequest: mockCallWithRequest, - registerGraphQLEndpoint: jest.fn(), - getIndexPatternsService: jest.fn(), - }; - jest.doMock('../framework', () => ({ - callWithRequest: mockCallWithRequest, - })); - - test('getOverviewHost', async () => { - const EsOverviewHost = new ElasticsearchOverviewAdapter(mockFramework); - const data: OverviewHostData = await EsOverviewHost.getOverviewHost( - mockRequestHost as FrameworkRequest, - mockOptionsHost - ); - expect(data).toEqual({ - inspect: { - dsl: [JSON.stringify(mockBuildOverviewHostQuery, null, 2)], - response: [JSON.stringify(mockNoDataResponse, null, 2)], - }, - auditbeatAuditd: 0, - auditbeatFIM: 0, - auditbeatLogin: 0, - auditbeatPackage: 0, - auditbeatProcess: 0, - auditbeatUser: 0, - endgameDns: 0, - endgameFile: 0, - endgameImageLoad: 0, - endgameNetwork: 0, - endgameProcess: 0, - endgameRegistry: 0, - endgameSecurity: 0, - filebeatSystemModule: 0, - winlogbeatSecurity: 0, - winlogbeatMWSysmonOperational: 0, - }); - }); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/server/lib/overview/elasticsearch_adapter.ts b/x-pack/plugins/security_solution/server/lib/overview/elasticsearch_adapter.ts deleted file mode 100644 index 982b47110c513..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/overview/elasticsearch_adapter.ts +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { getOr } from 'lodash/fp'; - -import { OverviewHostData, OverviewNetworkData } from '../../graphql/types'; -import { inspectStringifyObject } from '../../utils/build_query'; -import { FrameworkAdapter, FrameworkRequest, RequestBasicOptions } from '../framework'; -import { TermAggregation } from '../types'; - -import { buildOverviewHostQuery, buildOverviewNetworkQuery } from './query.dsl'; -import { OverviewAdapter, OverviewHostHit, OverviewNetworkHit } from './types'; - -export class ElasticsearchOverviewAdapter implements OverviewAdapter { - constructor(private readonly framework: FrameworkAdapter) {} - - public async getOverviewNetwork( - request: FrameworkRequest, - options: RequestBasicOptions - ): Promise { - const dsl = buildOverviewNetworkQuery(options); - const response = await this.framework.callWithRequest( - request, - 'search', - dsl - ); - const inspect = { - dsl: [inspectStringifyObject(dsl)], - response: [inspectStringifyObject(response)], - }; - - return { - inspect, - auditbeatSocket: getOr(null, 'aggregations.unique_socket_count.doc_count', response), - filebeatCisco: getOr( - null, - 'aggregations.unique_filebeat_count.unique_cisco_count.doc_count', - response - ), - filebeatNetflow: getOr( - null, - 'aggregations.unique_filebeat_count.unique_netflow_count.doc_count', - response - ), - filebeatPanw: getOr( - null, - 'aggregations.unique_filebeat_count.unique_panw_count.doc_count', - response - ), - filebeatSuricata: getOr(null, 'aggregations.unique_suricata_count.doc_count', response), - filebeatZeek: getOr(null, 'aggregations.unique_zeek_count.doc_count', response), - packetbeatDNS: getOr(null, 'aggregations.unique_dns_count.doc_count', response), - packetbeatFlow: getOr(null, 'aggregations.unique_flow_count.doc_count', response), - packetbeatTLS: getOr( - null, - 'aggregations.unique_packetbeat_count.unique_tls_count.doc_count', - response - ), - }; - } - - public async getOverviewHost( - request: FrameworkRequest, - options: RequestBasicOptions - ): Promise { - const dsl = buildOverviewHostQuery(options); - const response = await this.framework.callWithRequest( - request, - 'search', - dsl - ); - const inspect = { - dsl: [inspectStringifyObject(dsl)], - response: [inspectStringifyObject(response)], - }; - - return { - inspect, - auditbeatAuditd: getOr(null, 'aggregations.auditd_count.doc_count', response), - auditbeatFIM: getOr(null, 'aggregations.fim_count.doc_count', response), - auditbeatLogin: getOr(null, 'aggregations.system_module.login_count.doc_count', response), - auditbeatPackage: getOr(null, 'aggregations.system_module.package_count.doc_count', response), - auditbeatProcess: getOr(null, 'aggregations.system_module.process_count.doc_count', response), - auditbeatUser: getOr(null, 'aggregations.system_module.user_count.doc_count', response), - endgameDns: getOr(null, 'aggregations.endgame_module.dns_event_count.doc_count', response), - endgameFile: getOr(null, 'aggregations.endgame_module.file_event_count.doc_count', response), - endgameImageLoad: getOr( - null, - 'aggregations.endgame_module.image_load_event_count.doc_count', - response - ), - endgameNetwork: getOr( - null, - 'aggregations.endgame_module.network_event_count.doc_count', - response - ), - endgameProcess: getOr( - null, - 'aggregations.endgame_module.process_event_count.doc_count', - response - ), - endgameRegistry: getOr( - null, - 'aggregations.endgame_module.registry_event.doc_count', - response - ), - endgameSecurity: getOr( - null, - 'aggregations.endgame_module.security_event_count.doc_count', - response - ), - filebeatSystemModule: getOr( - null, - 'aggregations.system_module.filebeat_count.doc_count', - response - ), - winlogbeatSecurity: getOr( - null, - 'aggregations.winlog_module.security_event_count.doc_count', - response - ), - winlogbeatMWSysmonOperational: getOr( - null, - 'aggregations.winlog_module.mwsysmon_operational_event_count.doc_count', - response - ), - }; - } -} diff --git a/x-pack/plugins/security_solution/server/lib/overview/index.ts b/x-pack/plugins/security_solution/server/lib/overview/index.ts deleted file mode 100644 index ae9f81eb261a7..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/overview/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { OverviewHostData, OverviewNetworkData } from '../../graphql/types'; -import { FrameworkRequest, RequestBasicOptions } from '../framework'; - -import { OverviewAdapter } from './types'; - -export class Overview { - constructor(private readonly adapter: OverviewAdapter) {} - - public async getOverviewNetwork( - req: FrameworkRequest, - options: RequestBasicOptions - ): Promise { - return this.adapter.getOverviewNetwork(req, options); - } - - public async getOverviewHost( - req: FrameworkRequest, - options: RequestBasicOptions - ): Promise { - return this.adapter.getOverviewHost(req, options); - } -} diff --git a/x-pack/plugins/security_solution/server/lib/overview/mock.ts b/x-pack/plugins/security_solution/server/lib/overview/mock.ts deleted file mode 100644 index 2621c795ecd6b..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/overview/mock.ts +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { DEFAULT_INDEX_PATTERN } from '../../../common/constants'; -import { RequestBasicOptions } from '../framework/types'; - -export const mockOptionsNetwork: RequestBasicOptions = { - defaultIndex: DEFAULT_INDEX_PATTERN, - sourceConfiguration: { - fields: { - container: 'docker.container.name', - host: 'beat.hostname', - message: ['message', '@message'], - pod: 'kubernetes.pod.name', - tiebreaker: '_doc', - timestamp: '@timestamp', - }, - }, - timerange: { interval: '12h', to: '2019-02-11T02:26:46.071Z', from: '2019-02-10T02:26:46.071Z' }, - filterQuery: {}, -}; - -export const mockRequestNetwork = { - body: { - operationName: 'GetOverviewNetworkQuery', - variables: { - sourceId: 'default', - timerange: { - interval: '12h', - from: '2019-02-10T02:30:30.772Z', - to: '2019-02-11T02:30:30.772Z', - }, - filterQuery: '', - }, - query: - 'query GetOverviewNetworkQuery(\n $sourceId: ID!\n $timerange: TimerangeInput!\n $filterQuery: String\n ) {\n source(id: $sourceId) {\n id\n OverviewNetwork(timerange: $timerange, filterQuery: $filterQuery) {\n packetbeatFlow\n packetbeatDNS\n filebeatSuricata\n filebeatZeek\n auditbeatSocket\n }\n }\n }', - }, -}; - -export const mockResponseNetwork = { - took: 89, - timed_out: false, - _shards: { total: 18, successful: 18, skipped: 0, failed: 0 }, - hits: { total: { value: 950867, relation: 'eq' }, max_score: null, hits: [] }, - aggregations: { - unique_flow_count: { doc_count: 50243 }, - unique_dns_count: { doc_count: 15000 }, - unique_suricata_count: { doc_count: 2375 }, - unique_zeek_count: { doc_count: 456 }, - unique_socket_count: { doc_count: 13 }, - unique_filebeat_count: { - doc_count: 456756, - unique_cisco_count: { doc_count: 14 }, - unique_netflow_count: { doc_count: 992 }, - unique_panw_count: { doc_count: 225 }, - }, - unique_packetbeat_count: { doc_count: 7897896, unique_tls_count: { doc_count: 2009 } }, - }, -}; - -export const mockBuildOverviewHostQuery = { buildOverviewHostQuery: 'buildOverviewHostQuery' }; -export const mockBuildOverviewNetworkQuery = { - buildOverviewNetworkQuery: 'buildOverviewNetworkQuery', -}; - -export const mockResultNetwork = { - inspect: { - dsl: [JSON.stringify(mockBuildOverviewNetworkQuery, null, 2)], - response: [JSON.stringify(mockResponseNetwork, null, 2)], - }, - packetbeatFlow: 50243, - packetbeatDNS: 15000, - filebeatSuricata: 2375, - filebeatZeek: 456, - auditbeatSocket: 13, - filebeatCisco: 14, - filebeatNetflow: 992, - filebeatPanw: 225, - packetbeatTLS: 2009, -}; - -export const mockOptionsHost: RequestBasicOptions = { - defaultIndex: DEFAULT_INDEX_PATTERN, - sourceConfiguration: { - fields: { - container: 'docker.container.name', - host: 'beat.hostname', - message: ['message', '@message'], - pod: 'kubernetes.pod.name', - tiebreaker: '_doc', - timestamp: '@timestamp', - }, - }, - timerange: { interval: '12h', to: '2019-02-11T02:26:46.071Z', from: '2019-02-10T02:26:46.071Z' }, - filterQuery: {}, -}; - -export const mockRequestHost = { - body: { - operationName: 'GetOverviewHostQuery', - variables: { - sourceId: 'default', - timerange: { - interval: '12h', - from: '2019-02-10T02:30:30.772Z', - to: '2019-02-11T02:30:30.772Z', - }, - filterQuery: '', - }, - query: - 'query GetOverviewHostQuery(\n $sourceId: ID!\n $timerange: TimerangeInput!\n $filterQuery: String\n ) {\n source(id: $sourceId) {\n id\n OverviewHost(timerange: $timerange, filterQuery: $filterQuery) {\n auditbeatAuditd\n auditbeatFIM\n auditbeatLogin\n auditbeatPackage\n auditbeatProcess\n auditbeatUser\n }\n }\n }', - }, -}; - -export const mockResponseHost = { - took: 89, - timed_out: false, - _shards: { total: 18, successful: 18, skipped: 0, failed: 0 }, - hits: { total: { value: 950867, relation: 'eq' }, max_score: null, hits: [] }, - aggregations: { - auditd_count: { doc_count: 73847 }, - endgame_module: { - doc_count: 6258, - dns_event_count: { doc_count: 891 }, - file_event_count: { doc_count: 892 }, - image_load_event_count: { doc_count: 893 }, - network_event_count: { doc_count: 894 }, - process_event_count: { doc_count: 895 }, - registry_event: { doc_count: 896 }, - security_event_count: { doc_count: 897 }, - }, - fim_count: { doc_count: 107307 }, - system_module: { - doc_count: 20000000, - login_count: { doc_count: 60015 }, - package_count: { doc_count: 2003 }, - process_count: { doc_count: 1200 }, - user_count: { doc_count: 1979 }, - filebeat_count: { doc_count: 225 }, - }, - winlog_module: { - security_event_count: { - doc_count: 523, - }, - mwsysmon_operational_event_count: { - doc_count: 214, - }, - }, - }, -}; - -export const mockResultHost = { - inspect: { - dsl: [JSON.stringify(mockBuildOverviewHostQuery, null, 2)], - response: [JSON.stringify(mockResponseHost, null, 2)], - }, - auditbeatAuditd: 73847, - auditbeatFIM: 107307, - auditbeatLogin: 60015, - auditbeatPackage: 2003, - auditbeatProcess: 1200, - auditbeatUser: 1979, - endgameDns: 891, - endgameFile: 892, - endgameImageLoad: 893, - endgameNetwork: 894, - endgameProcess: 895, - endgameRegistry: 896, - endgameSecurity: 897, - filebeatSystemModule: 225, - winlogbeatSecurity: 523, - winlogbeatMWSysmonOperational: 214, -}; diff --git a/x-pack/plugins/security_solution/server/lib/overview/query.dsl.ts b/x-pack/plugins/security_solution/server/lib/overview/query.dsl.ts deleted file mode 100644 index b6b1cfea394fd..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/overview/query.dsl.ts +++ /dev/null @@ -1,397 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { createQueryFilterClauses } from '../../utils/build_query'; -import { RequestBasicOptions } from '../framework'; - -export const buildOverviewNetworkQuery = ({ - filterQuery, - timerange: { from, to }, - defaultIndex, - sourceConfiguration: { - fields: { timestamp }, - }, -}: RequestBasicOptions) => { - const filter = [ - ...createQueryFilterClauses(filterQuery), - { - range: { - [timestamp]: { - gte: from, - lte: to, - format: 'strict_date_optional_time', - }, - }, - }, - ]; - - const dslQuery = { - allowNoIndices: true, - index: defaultIndex, - ignoreUnavailable: true, - body: { - aggregations: { - unique_flow_count: { - filter: { - term: { type: 'flow' }, - }, - }, - unique_dns_count: { - filter: { - term: { type: 'dns' }, - }, - }, - unique_suricata_count: { - filter: { - term: { 'service.type': 'suricata' }, - }, - }, - unique_zeek_count: { - filter: { - term: { 'service.type': 'zeek' }, - }, - }, - unique_socket_count: { - filter: { - term: { 'event.dataset': 'socket' }, - }, - }, - unique_filebeat_count: { - filter: { - term: { 'agent.type': 'filebeat' }, - }, - aggs: { - unique_netflow_count: { - filter: { - term: { 'input.type': 'netflow' }, - }, - }, - unique_panw_count: { - filter: { - term: { 'event.module': 'panw' }, - }, - }, - unique_cisco_count: { - filter: { - term: { 'event.module': 'cisco' }, - }, - }, - }, - }, - unique_packetbeat_count: { - filter: { - term: { 'agent.type': 'packetbeat' }, - }, - aggs: { - unique_tls_count: { - filter: { - term: { 'network.protocol': 'tls' }, - }, - }, - }, - }, - }, - query: { - bool: { - filter, - }, - }, - size: 0, - track_total_hits: false, - }, - }; - - return dslQuery; -}; - -export const buildOverviewHostQuery = ({ - filterQuery, - timerange: { from, to }, - defaultIndex, - sourceConfiguration: { - fields: { timestamp }, - }, -}: RequestBasicOptions) => { - const filter = [ - ...createQueryFilterClauses(filterQuery), - { - range: { - [timestamp]: { - gte: from, - lte: to, - format: 'strict_date_optional_time', - }, - }, - }, - ]; - - const dslQuery = { - allowNoIndices: true, - index: defaultIndex, - ignoreUnavailable: true, - body: { - aggregations: { - auditd_count: { - filter: { - term: { - 'event.module': 'auditd', - }, - }, - }, - endgame_module: { - filter: { - bool: { - should: [ - { - term: { 'event.module': 'endpoint' }, - }, - { - term: { - 'event.module': 'endgame', - }, - }, - ], - }, - }, - aggs: { - dns_event_count: { - filter: { - bool: { - should: [ - { - bool: { - filter: [ - { term: { 'network.protocol': 'dns' } }, - { term: { 'event.category': 'network' } }, - ], - }, - }, - { - term: { - 'endgame.event_type_full': 'dns_event', - }, - }, - ], - }, - }, - }, - file_event_count: { - filter: { - bool: { - should: [ - { - term: { - 'event.category': 'file', - }, - }, - { - term: { - 'endgame.event_type_full': 'file_event', - }, - }, - ], - }, - }, - }, - image_load_event_count: { - filter: { - bool: { - should: [ - { - bool: { - should: [ - { - term: { - 'event.category': 'library', - }, - }, - { - term: { - 'event.category': 'driver', - }, - }, - ], - }, - }, - { - term: { - 'endgame.event_type_full': 'image_load_event', - }, - }, - ], - }, - }, - }, - network_event_count: { - filter: { - bool: { - should: [ - { - bool: { - filter: [ - { - bool: { - must_not: { - term: { 'network.protocol': 'dns' }, - }, - }, - }, - { - term: { 'event.category': 'network' }, - }, - ], - }, - }, - { - term: { - 'endgame.event_type_full': 'network_event', - }, - }, - ], - }, - }, - }, - process_event_count: { - filter: { - bool: { - should: [ - { - term: { 'event.category': 'process' }, - }, - { - term: { - 'endgame.event_type_full': 'process_event', - }, - }, - ], - }, - }, - }, - registry_event: { - filter: { - bool: { - should: [ - { - term: { 'event.category': 'registry' }, - }, - { - term: { - 'endgame.event_type_full': 'registry_event', - }, - }, - ], - }, - }, - }, - security_event_count: { - filter: { - bool: { - should: [ - { - bool: { - filter: [ - { term: { 'event.category': 'session' } }, - { term: { 'event.category': 'authentication' } }, - ], - }, - }, - { - term: { - 'endgame.event_type_full': 'security_event', - }, - }, - ], - }, - }, - }, - }, - }, - fim_count: { - filter: { - term: { - 'event.module': 'file_integrity', - }, - }, - }, - winlog_module: { - filter: { - term: { - 'agent.type': 'winlogbeat', - }, - }, - aggs: { - mwsysmon_operational_event_count: { - filter: { - term: { - 'winlog.channel': 'Microsoft-Windows-Sysmon/Operational', - }, - }, - }, - security_event_count: { - filter: { - term: { - 'winlog.channel': 'Security', - }, - }, - }, - }, - }, - system_module: { - filter: { - term: { - 'event.module': 'system', - }, - }, - aggs: { - login_count: { - filter: { - term: { - 'event.dataset': 'login', - }, - }, - }, - package_count: { - filter: { - term: { - 'event.dataset': 'package', - }, - }, - }, - process_count: { - filter: { - term: { - 'event.dataset': 'process', - }, - }, - }, - user_count: { - filter: { - term: { - 'event.dataset': 'user', - }, - }, - }, - filebeat_count: { - filter: { - term: { - 'agent.type': 'filebeat', - }, - }, - }, - }, - }, - }, - query: { - bool: { - filter, - }, - }, - size: 0, - track_total_hits: false, - }, - }; - - return dslQuery; -}; diff --git a/x-pack/plugins/security_solution/server/lib/overview/types.ts b/x-pack/plugins/security_solution/server/lib/overview/types.ts deleted file mode 100644 index 7fdad08ac9b37..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/overview/types.ts +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { OverviewHostData, OverviewNetworkData } from '../../graphql/types'; -import { FrameworkRequest, RequestBasicOptions } from '../framework'; -import { SearchHit } from '../types'; - -export interface OverviewAdapter { - getOverviewNetwork( - request: FrameworkRequest, - options: RequestBasicOptions - ): Promise; - getOverviewHost( - request: FrameworkRequest, - options: RequestBasicOptions - ): Promise; -} - -export interface OverviewNetworkHit extends SearchHit { - aggregations: { - unique_flow_count: { - doc_count: number; - }; - unique_dns_count: { - doc_count: number; - }; - unique_suricata_count: { - doc_count: number; - }; - unique_zeek_count: { - doc_count: number; - }; - unique_socket_count: { - doc_count: number; - }; - unique_filebeat_count: { - unique_netflow_count: { - doc_count: number; - }; - unique_panw_count: { - doc_count: number; - }; - unique_cisco_count: { - doc_count: number; - }; - }; - unique_packetbeat_count: { - unique_tls_count: { - doc_count: number; - }; - }; - }; -} - -export interface OverviewHostHit extends SearchHit { - aggregations: { - auditd_count: { - doc_count: number; - }; - endgame_module: { - dns_event_count: { - doc_count: number; - }; - file_event_count: { - doc_count: number; - }; - image_load_event_count: { - doc_count: number; - }; - network_event_count: { - doc_count: number; - }; - process_event_count: { - doc_count: number; - }; - registry_event: { - doc_count: number; - }; - security_event_count: { - doc_count: number; - }; - }; - fim_count: { - doc_count: number; - }; - system_module: { - login_count: { - doc_count: number; - }; - package_count: { - doc_count: number; - }; - process_count: { - doc_count: number; - }; - user_count: { - doc_count: number; - }; - filebeat_count: { - doc_count: number; - }; - }; - winlog_count: { - doc_count: number; - }; - }; -} diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings.ts index c5ee611dfa27f..11082cd7295cc 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings.ts @@ -213,6 +213,9 @@ export const timelineSavedObjectMappings: SavedObjectsType['mappings'] = { }, }, }, + indexNames: { + type: 'text', + }, kqlMode: { type: 'keyword', }, diff --git a/x-pack/plugins/security_solution/server/lib/tls/elasticsearch_adapter.test.ts b/x-pack/plugins/security_solution/server/lib/tls/elasticsearch_adapter.test.ts deleted file mode 100644 index 428685cbaddb8..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/tls/elasticsearch_adapter.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { buildTlsQuery } from './query_tls.dsl'; -import { ElasticsearchTlsAdapter } from './elasticsearch_adapter'; -import expect from '@kbn/expect'; -import { FrameworkRequest, FrameworkAdapter } from '../framework'; -import { mockRequest, mockResponse, mockOptions, expectedTlsEdges, mockTlsQuery } from './mock'; -import { TlsData } from '../../graphql/types'; - -jest.mock('./query_tls.dsl', () => { - return { - buildTlsQuery: jest.fn(), - }; -}); - -describe('elasticsearch_adapter', () => { - describe('#getTls', () => { - let data: TlsData; - const mockCallWithRequest = jest.fn(); - const mockFramework: FrameworkAdapter = { - callWithRequest: mockCallWithRequest, - registerGraphQLEndpoint: jest.fn(), - getIndexPatternsService: jest.fn(), - }; - - beforeAll(async () => { - (buildTlsQuery as jest.Mock).mockReset(); - (buildTlsQuery as jest.Mock).mockReturnValue(mockTlsQuery); - - mockCallWithRequest.mockResolvedValue(mockResponse); - jest.doMock('../framework', () => ({ - callWithRequest: mockCallWithRequest, - })); - - const EsTls = new ElasticsearchTlsAdapter(mockFramework); - data = await EsTls.getTls(mockRequest as FrameworkRequest, mockOptions); - }); - - afterAll(() => { - mockCallWithRequest.mockRestore(); - (buildTlsQuery as jest.Mock).mockClear(); - }); - - test('buildTlsQuery', () => { - expect((buildTlsQuery as jest.Mock).mock.calls[0][0]).to.eql(mockOptions); - }); - - test('will return tlsEdges correctly', () => { - expect(data.edges).to.eql(expectedTlsEdges); - }); - - test('will return inspect data', () => { - expect(data.inspect).to.eql({ - dsl: [JSON.stringify(mockTlsQuery, null, 2)], - response: [JSON.stringify(mockResponse, null, 2)], - }); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/server/lib/tls/elasticsearch_adapter.ts b/x-pack/plugins/security_solution/server/lib/tls/elasticsearch_adapter.ts deleted file mode 100644 index ab9175951a8f5..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/tls/elasticsearch_adapter.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { getOr } from 'lodash/fp'; - -import { TlsData, TlsEdges } from '../../graphql/types'; -import { inspectStringifyObject } from '../../utils/build_query'; -import { DatabaseSearchResponse, FrameworkAdapter, FrameworkRequest } from '../framework'; -import { TermAggregation } from '../types'; -import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../common/constants'; -import { TlsRequestOptions } from './index'; - -import { TlsAdapter, TlsBuckets } from './types'; - -import { buildTlsQuery } from './query_tls.dsl'; - -export class ElasticsearchTlsAdapter implements TlsAdapter { - constructor(private readonly framework: FrameworkAdapter) {} - - public async getTls(request: FrameworkRequest, options: TlsRequestOptions): Promise { - if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { - throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); - } - const dsl = buildTlsQuery(options); - const response = await this.framework.callWithRequest( - request, - 'search', - dsl - ); - - const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; - const totalCount = getOr(0, 'aggregations.count.value', response); - const tlsEdges: TlsEdges[] = getTlsEdges(response, options); - const fakeTotalCount = fakePossibleCount <= totalCount ? fakePossibleCount : totalCount; - const edges = tlsEdges.splice(cursorStart, querySize - cursorStart); - const inspect = { - dsl: [inspectStringifyObject(dsl)], - response: [inspectStringifyObject(response)], - }; - const showMorePagesIndicator = totalCount > fakeTotalCount; - return { - edges, - inspect, - pageInfo: { - activePage: activePage ? activePage : 0, - fakeTotalCount, - showMorePagesIndicator, - }, - totalCount, - }; - } -} - -const getTlsEdges = ( - response: DatabaseSearchResponse, - options: TlsRequestOptions -): TlsEdges[] => { - return formatTlsEdges(getOr([], 'aggregations.sha1.buckets', response)); -}; - -export const formatTlsEdges = (buckets: TlsBuckets[]): TlsEdges[] => { - return buckets.map((bucket: TlsBuckets) => { - const edge: TlsEdges = { - node: { - _id: bucket.key, - subjects: bucket.subjects.buckets.map(({ key }) => key), - ja3: bucket.ja3.buckets.map(({ key }) => key), - issuers: bucket.issuers.buckets.map(({ key }) => key), - // eslint-disable-next-line @typescript-eslint/naming-convention - notAfter: bucket.not_after.buckets.map(({ key_as_string }) => key_as_string), - }, - cursor: { - value: bucket.key, - tiebreaker: null, - }, - }; - return edge; - }); -}; diff --git a/x-pack/plugins/security_solution/server/lib/tls/index.ts b/x-pack/plugins/security_solution/server/lib/tls/index.ts deleted file mode 100644 index 25e3957cc99db..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/tls/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { FlowTargetSourceDest, TlsSortField, TlsData } from '../../graphql/types'; -import { FrameworkRequest, RequestOptionsPaginated } from '../framework'; - -import { TlsAdapter } from './types'; - -export * from './elasticsearch_adapter'; - -export interface TlsRequestOptions extends RequestOptionsPaginated { - ip?: string; - sort: TlsSortField; - flowTarget: FlowTargetSourceDest; -} - -export class TLS { - constructor(private readonly adapter: TlsAdapter) {} - - public async getTls(req: FrameworkRequest, options: TlsRequestOptions): Promise { - return this.adapter.getTls(req, options); - } -} diff --git a/x-pack/plugins/security_solution/server/lib/tls/mock.ts b/x-pack/plugins/security_solution/server/lib/tls/mock.ts deleted file mode 100644 index 62d5e1e61570a..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/tls/mock.ts +++ /dev/null @@ -1,481 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Direction, TlsFields, FlowTargetSourceDest } from '../../graphql/types'; - -export const mockTlsQuery = { - allowNoIndices: true, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - ignoreUnavailable: true, - body: { - aggs: { - count: { cardinality: { field: 'tls.server_certificate.fingerprint.sha1' } }, - sha1: { - terms: { - field: 'tls.server_certificate.fingerprint.sha1', - size: 10, - order: { _key: 'desc' }, - }, - aggs: { - issuers: { terms: { field: 'tls.server.issuer' } }, - subjects: { terms: { field: 'tls.server.subject' } }, - not_after: { terms: { field: 'tls.server.not_after' } }, - ja3: { terms: { field: 'tls.server.ja3s' } }, - }, - }, - }, - query: { - bool: { filter: [{ range: { '@timestamp': { gte: 1570719927430, lte: 1570806327431 } } }] }, - }, - size: 0, - track_total_hits: false, - }, -}; - -export const expectedTlsEdges = [ - { - cursor: { - tiebreaker: null, - value: 'fff8dc95436e0e25ce46b1526a1a547e8cf3bb82', - }, - node: { - _id: 'fff8dc95436e0e25ce46b1526a1a547e8cf3bb82', - subjects: ['*.1.nflxso.net'], - issuers: ['DigiCert SHA2 Secure Server CA'], - ja3: ['95d2dd53a89b334cddd5c22e81e7fe61'], - notAfter: ['2019-10-27T12:00:00.000Z'], - }, - }, - { - cursor: { - tiebreaker: null, - value: 'fd8440c4b20978b173e0910e2639d114f0d405c5', - }, - node: { - _id: 'fd8440c4b20978b173e0910e2639d114f0d405c5', - subjects: ['cogocast.net'], - issuers: ['Amazon'], - ja3: ['a111d93cdf31f993c40a8a9ef13e8d7e'], - notAfter: ['2020-02-01T12:00:00.000Z'], - }, - }, - { - cursor: { tiebreaker: null, value: 'fcdc16645ebb3386adc96e7ba735c4745709b9dd' }, - node: { - _id: 'fcdc16645ebb3386adc96e7ba735c4745709b9dd', - subjects: ['player-devintever2.mountain.siriusxm.com'], - issuers: ['Trustwave Organization Validation SHA256 CA, Level 1'], - ja3: ['6fa3244afc6bb6f9fad207b6b52af26b'], - notAfter: ['2020-03-06T21:57:09.000Z'], - }, - }, - { - cursor: { tiebreaker: null, value: 'fccf375789cb7e671502a7b0cc969f218a4b2c70' }, - node: { - _id: 'fccf375789cb7e671502a7b0cc969f218a4b2c70', - subjects: ['appleid.apple.com'], - issuers: ['DigiCert SHA2 Extended Validation Server CA'], - ja3: ['6fa3244afc6bb6f9fad207b6b52af26b'], - notAfter: ['2020-07-04T12:00:00.000Z'], - }, - }, - { - cursor: { tiebreaker: null, value: 'fc4a296b706fa18ac50b96f5c0327c69db4a8981' }, - node: { - _id: 'fc4a296b706fa18ac50b96f5c0327c69db4a8981', - subjects: ['itunes.apple.com'], - issuers: ['DigiCert SHA2 Extended Validation Server CA'], - ja3: ['a441a33aaee795f498d6b764cc78989a'], - notAfter: ['2020-03-24T12:00:00.000Z'], - }, - }, - { - cursor: { tiebreaker: null, value: 'fc2cbc41f6a0e9c0118de4fe40f299f7207b797e' }, - node: { - _id: 'fc2cbc41f6a0e9c0118de4fe40f299f7207b797e', - subjects: ['incapsula.com'], - issuers: ['GlobalSign CloudSSL CA - SHA256 - G3'], - ja3: ['6fa3244afc6bb6f9fad207b6b52af26b'], - notAfter: ['2020-04-04T14:05:06.000Z'], - }, - }, - { - cursor: { tiebreaker: null, value: 'fb70d78ffa663a3a4374d841b3288d2de9759566' }, - node: { - _id: 'fb70d78ffa663a3a4374d841b3288d2de9759566', - subjects: ['*.siriusxm.com'], - issuers: ['DigiCert Baltimore CA-2 G2'], - ja3: ['535aca3d99fc247509cd50933cd71d37', '6fa3244afc6bb6f9fad207b6b52af26b'], - notAfter: ['2021-10-27T12:00:00.000Z'], - }, - }, - { - cursor: { tiebreaker: null, value: 'fb59038dcec33ab3a01a6ae60d0835ad0e04ccf0' }, - node: { - _id: 'fb59038dcec33ab3a01a6ae60d0835ad0e04ccf0', - subjects: ['photos.amazon.eu'], - issuers: ['Amazon'], - ja3: ['6fa3244afc6bb6f9fad207b6b52af26b'], - notAfter: ['2020-04-23T12:00:00.000Z'], - }, - }, - { - cursor: { tiebreaker: null, value: 'f9815293c883a6006f0b2d95a4895bdc501fd174' }, - node: { - _id: 'f9815293c883a6006f0b2d95a4895bdc501fd174', - subjects: ['cdn.hbo.com'], - issuers: ['Sectigo RSA Organization Validation Secure Server CA'], - ja3: ['6fa3244afc6bb6f9fad207b6b52af26b'], - notAfter: ['2021-02-10T23:59:59.000Z'], - }, - }, - { - cursor: { tiebreaker: null, value: 'f8db6a69797e383dca2529727369595733123386' }, - node: { - _id: 'f8db6a69797e383dca2529727369595733123386', - subjects: ['www.google.com'], - issuers: ['GTS CA 1O1'], - ja3: ['a111d93cdf31f993c40a8a9ef13e8d7e'], - notAfter: ['2019-12-10T13:32:54.000Z'], - }, - }, -]; - -export const mockRequest = { - body: { - operationName: 'GetTlsQuery', - variables: { - defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - filterQuery: '', - flowTarget: 'source', - inspect: false, - ip: '', - pagination: { activePage: 0, cursorStart: 0, fakePossibleCount: 50, querySize: 10 }, - sort: { field: '_id', direction: 'desc' }, - sourceId: 'default', - timerange: { interval: '12h', from: 1570716261267, to: 1570802661267 }, - }, - query: - 'query GetTlsQuery($sourceId: ID!, $filterQuery: String, $flowTarget: FlowTarget!, $ip: String!, $pagination: PaginationInputPaginated!, $sort: TlsSortField!, $timerange: TimerangeInput!, $defaultIndex: [String!]!, $inspect: Boolean!) {\n source(id: $sourceId) {\n id\n Tls(filterQuery: $filterQuery, flowTarget: $flowTarget, ip: $ip, pagination: $pagination, sort: $sort, timerange: $timerange, defaultIndex: $defaultIndex) {\n totalCount\n edges {\n node {\n _id\n subjects\n ja3\n issuers\n notAfter\n __typename\n }\n cursor {\n value\n __typename\n }\n __typename\n }\n pageInfo {\n activePage\n fakeTotalCount\n showMorePagesIndicator\n __typename\n }\n inspect @include(if: $inspect) {\n dsl\n response\n __typename\n }\n __typename\n }\n __typename\n }\n}\n', - }, -}; - -export const mockResponse = { - took: 92, - timed_out: false, - _shards: { total: 33, successful: 33, skipped: 0, failed: 0 }, - hits: { max_score: null, hits: [] }, - aggregations: { - sha1: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 4597, - buckets: [ - { - key: 'fff8dc95436e0e25ce46b1526a1a547e8cf3bb82', - doc_count: 1, - not_after: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [ - { key: 1572177600000, key_as_string: '2019-10-27T12:00:00.000Z', doc_count: 1 }, - ], - }, - issuers: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: 'DigiCert SHA2 Secure Server CA', doc_count: 1 }], - }, - subjects: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: '*.1.nflxso.net', doc_count: 1 }], - }, - ja3: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: '95d2dd53a89b334cddd5c22e81e7fe61', doc_count: 1 }], - }, - }, - { - key: 'fd8440c4b20978b173e0910e2639d114f0d405c5', - doc_count: 1, - not_after: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [ - { key: 1580558400000, key_as_string: '2020-02-01T12:00:00.000Z', doc_count: 1 }, - ], - }, - issuers: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: 'Amazon', doc_count: 1 }], - }, - subjects: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: 'cogocast.net', doc_count: 1 }], - }, - ja3: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: 'a111d93cdf31f993c40a8a9ef13e8d7e', doc_count: 1 }], - }, - }, - { - key: 'fcdc16645ebb3386adc96e7ba735c4745709b9dd', - doc_count: 1, - not_after: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [ - { key: 1583531829000, key_as_string: '2020-03-06T21:57:09.000Z', doc_count: 1 }, - ], - }, - issuers: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [ - { key: 'Trustwave Organization Validation SHA256 CA, Level 1', doc_count: 1 }, - ], - }, - subjects: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: 'player-devintever2.mountain.siriusxm.com', doc_count: 1 }], - }, - ja3: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: '6fa3244afc6bb6f9fad207b6b52af26b', doc_count: 1 }], - }, - }, - { - key: 'fccf375789cb7e671502a7b0cc969f218a4b2c70', - doc_count: 1, - not_after: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [ - { key: 1593864000000, key_as_string: '2020-07-04T12:00:00.000Z', doc_count: 1 }, - ], - }, - issuers: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: 'DigiCert SHA2 Extended Validation Server CA', doc_count: 1 }], - }, - subjects: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: 'appleid.apple.com', doc_count: 1 }], - }, - ja3: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: '6fa3244afc6bb6f9fad207b6b52af26b', doc_count: 1 }], - }, - }, - { - key: 'fc4a296b706fa18ac50b96f5c0327c69db4a8981', - doc_count: 2, - not_after: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [ - { key: 1585051200000, key_as_string: '2020-03-24T12:00:00.000Z', doc_count: 2 }, - ], - }, - issuers: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: 'DigiCert SHA2 Extended Validation Server CA', doc_count: 2 }], - }, - subjects: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: 'itunes.apple.com', doc_count: 2 }], - }, - ja3: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: 'a441a33aaee795f498d6b764cc78989a', doc_count: 2 }], - }, - }, - { - key: 'fc2cbc41f6a0e9c0118de4fe40f299f7207b797e', - doc_count: 1, - not_after: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [ - { key: 1586009106000, key_as_string: '2020-04-04T14:05:06.000Z', doc_count: 1 }, - ], - }, - issuers: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: 'GlobalSign CloudSSL CA - SHA256 - G3', doc_count: 1 }], - }, - subjects: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: 'incapsula.com', doc_count: 1 }], - }, - ja3: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: '6fa3244afc6bb6f9fad207b6b52af26b', doc_count: 1 }], - }, - }, - { - key: 'fb70d78ffa663a3a4374d841b3288d2de9759566', - doc_count: 325, - not_after: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [ - { key: 1635336000000, key_as_string: '2021-10-27T12:00:00.000Z', doc_count: 325 }, - ], - }, - issuers: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: 'DigiCert Baltimore CA-2 G2', doc_count: 325 }], - }, - subjects: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: '*.siriusxm.com', doc_count: 325 }], - }, - ja3: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [ - { key: '535aca3d99fc247509cd50933cd71d37', doc_count: 284 }, - { key: '6fa3244afc6bb6f9fad207b6b52af26b', doc_count: 39 }, - ], - }, - }, - { - key: 'fb59038dcec33ab3a01a6ae60d0835ad0e04ccf0', - doc_count: 5, - not_after: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [ - { key: 1587643200000, key_as_string: '2020-04-23T12:00:00.000Z', doc_count: 5 }, - ], - }, - issuers: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: 'Amazon', doc_count: 5 }], - }, - subjects: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: 'photos.amazon.eu', doc_count: 5 }], - }, - ja3: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: '6fa3244afc6bb6f9fad207b6b52af26b', doc_count: 5 }], - }, - }, - { - key: 'f9815293c883a6006f0b2d95a4895bdc501fd174', - doc_count: 29, - not_after: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [ - { key: 1613001599000, key_as_string: '2021-02-10T23:59:59.000Z', doc_count: 29 }, - ], - }, - issuers: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [ - { key: 'Sectigo RSA Organization Validation Secure Server CA', doc_count: 29 }, - ], - }, - subjects: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: 'cdn.hbo.com', doc_count: 29 }], - }, - ja3: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: '6fa3244afc6bb6f9fad207b6b52af26b', doc_count: 26 }], - }, - }, - { - key: 'f8db6a69797e383dca2529727369595733123386', - doc_count: 5, - not_after: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [ - { key: 1575984774000, key_as_string: '2019-12-10T13:32:54.000Z', doc_count: 5 }, - ], - }, - issuers: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: 'GTS CA 1O1', doc_count: 5 }], - }, - subjects: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: 'www.google.com', doc_count: 5 }], - }, - ja3: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [{ key: 'a111d93cdf31f993c40a8a9ef13e8d7e', doc_count: 5 }], - }, - }, - ], - }, - count: { value: 364 }, - }, -}; - -export const mockOptions = { - defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - sourceConfiguration: { - fields: { - container: 'docker.container.name', - host: 'beat.hostname', - message: ['message', '@message'], - pod: 'kubernetes.pod.name', - tiebreaker: '_doc', - timestamp: '@timestamp', - }, - }, - timerange: { interval: '12h', to: '2019-10-11T13:51:11.626Z', from: '2019-10-10T13:51:11.626Z' }, - pagination: { activePage: 0, cursorStart: 0, fakePossibleCount: 50, querySize: 10 }, - filterQuery: {}, - fields: [ - 'totalCount', - '_id', - 'subjects', - 'ja3', - 'issuers', - 'notAfter', - 'edges.cursor.value', - 'pageInfo.activePage', - 'pageInfo.fakeTotalCount', - 'pageInfo.showMorePagesIndicator', - 'inspect.dsl', - 'inspect.response', - ], - ip: '', - sort: { field: TlsFields._id, direction: Direction.desc }, - flowTarget: FlowTargetSourceDest.source, -}; diff --git a/x-pack/plugins/security_solution/server/lib/tls/query_tls.dsl.ts b/x-pack/plugins/security_solution/server/lib/tls/query_tls.dsl.ts deleted file mode 100644 index f6921ddcdf508..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/tls/query_tls.dsl.ts +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { assertUnreachable } from '../../../common/utility_types'; -import { createQueryFilterClauses } from '../../utils/build_query'; - -import { TlsRequestOptions } from './index'; -import { TlsSortField, Direction, TlsFields } from '../../graphql/types'; - -const getAggs = (querySize: number, sort: TlsSortField) => ({ - count: { - cardinality: { - field: 'tls.server.hash.sha1', - }, - }, - sha1: { - terms: { - field: 'tls.server.hash.sha1', - size: querySize, - order: { - ...getQueryOrder(sort), - }, - }, - aggs: { - issuers: { - terms: { - field: 'tls.server.issuer', - }, - }, - subjects: { - terms: { - field: 'tls.server.subject', - }, - }, - not_after: { - terms: { - field: 'tls.server.not_after', - }, - }, - ja3: { - terms: { - field: 'tls.server.ja3s', - }, - }, - }, - }, -}); - -export const buildTlsQuery = ({ - ip, - sort, - filterQuery, - flowTarget, - pagination: { querySize }, - defaultIndex, - sourceConfiguration: { - fields: { timestamp }, - }, - timerange: { from, to }, -}: TlsRequestOptions) => { - const defaultFilter = [ - ...createQueryFilterClauses(filterQuery), - { - range: { - [timestamp]: { gte: from, lte: to, format: 'strict_date_optional_time' }, - }, - }, - ]; - - const filter = ip ? [...defaultFilter, { term: { [`${flowTarget}.ip`]: ip } }] : defaultFilter; - - const dslQuery = { - allowNoIndices: true, - index: defaultIndex, - ignoreUnavailable: true, - body: { - aggs: { - ...getAggs(querySize, sort), - }, - query: { - bool: { - filter, - }, - }, - size: 0, - track_total_hits: false, - }, - }; - - return dslQuery; -}; - -interface QueryOrder { - _key: Direction; -} - -const getQueryOrder = (sort: TlsSortField): QueryOrder => { - switch (sort.field) { - case TlsFields._id: - return { _key: sort.direction }; - default: - return assertUnreachable(sort.field); - } -}; diff --git a/x-pack/plugins/security_solution/server/lib/tls/types.ts b/x-pack/plugins/security_solution/server/lib/tls/types.ts deleted file mode 100644 index f18ddc04e14a0..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/tls/types.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { FrameworkRequest, RequestBasicOptions } from '../framework'; -import { TlsData } from '../../graphql/types'; - -export interface TlsAdapter { - getTls(request: FrameworkRequest, options: RequestBasicOptions): Promise; -} - -export interface TlsBuckets { - key: string; - timestamp?: { - value: number; - value_as_string: string; - }; - - subjects: { - buckets: Readonly>; - }; - - ja3: { - buckets: Readonly>; - }; - - issuers: { - buckets: Readonly>; - }; - - not_after: { - buckets: Readonly>; - }; -} diff --git a/x-pack/plugins/security_solution/server/lib/types.ts b/x-pack/plugins/security_solution/server/lib/types.ts index ff89512124b66..3c7c1cd3d7cff 100644 --- a/x-pack/plugins/security_solution/server/lib/types.ts +++ b/x-pack/plugins/security_solution/server/lib/types.ts @@ -17,14 +17,11 @@ import { IpDetails } from './ip_details'; import { KpiHosts } from './kpi_hosts'; import { KpiNetwork } from './kpi_network'; import { Network } from './network'; -import { Overview } from './overview'; import { SourceStatus } from './source_status'; import { Sources } from './sources'; -import { UncommonProcesses } from './uncommon_processes'; import { Note } from './note/saved_object'; import { PinnedEvent } from './pinned_event/saved_object'; import { Timeline } from './timeline/saved_object'; -import { TLS } from './tls'; import { MatrixHistogram } from './matrix_histogram'; export * from './hosts'; @@ -38,10 +35,7 @@ export interface AppDomainLibs { matrixHistogram: MatrixHistogram; network: Network; kpiNetwork: KpiNetwork; - overview: Overview; - uncommonProcesses: UncommonProcesses; kpiHosts: KpiHosts; - tls: TLS; } export interface AppBackendLibs extends AppDomainLibs { @@ -98,6 +92,23 @@ export interface ShardsResponse { successful: number; failed: number; skipped: number; + failures?: ShardError[]; +} + +export interface ShardError { + shard: number; + index: string; + node: string; + reason: { + type: string; + reason: string; + index_uuid: string; + index: string; + caused_by: { + type: string; + reason: string; + }; + }; } export interface Explanation { diff --git a/x-pack/plugins/security_solution/server/lib/uncommon_processes/elasticsearch_adapter.test.ts b/x-pack/plugins/security_solution/server/lib/uncommon_processes/elasticsearch_adapter.test.ts deleted file mode 100644 index 2a15f1fe074f8..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/uncommon_processes/elasticsearch_adapter.test.ts +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { UncommonProcessesEdges } from '../../graphql/types'; -import { processFieldsMap } from '../ecs_fields'; - -import { formatUncommonProcessesData, getHosts } from './elasticsearch_adapter'; -import { UncommonProcessBucket, UncommonProcessHit } from './types'; - -describe('elasticsearch_adapter', () => { - describe('#getHosts', () => { - const bucket1: UncommonProcessBucket = { - key: '123', - hosts: { - buckets: [ - { - key: '123', - host: { - hits: { - total: 0, - max_score: 0, - hits: [ - { - _index: 'hit-1', - _type: 'type-1', - _id: 'id-1', - _score: 0, - _source: { - host: { - name: ['host-1'], - id: ['host-id-1'], - }, - }, - }, - ], - }, - }, - }, - ], - }, - process: { - hits: { - total: { - value: 1, - relation: 'eq', - }, - max_score: 5, - hits: [], - }, - }, - }; - const bucket2: UncommonProcessBucket = { - key: '345', - hosts: { - buckets: [ - { - key: '123', - host: { - hits: { - total: 0, - max_score: 0, - hits: [ - { - _index: 'hit-1', - _type: 'type-1', - _id: 'id-1', - _score: 0, - _source: { - host: { - name: ['host-1'], - id: ['host-id-1'], - }, - }, - }, - ], - }, - }, - }, - { - key: '345', - host: { - hits: { - total: 0, - max_score: 0, - hits: [ - { - _index: 'hit-2', - _type: 'type-2', - _id: 'id-2', - _score: 0, - _source: { - host: { - name: ['host-2'], - id: ['host-id-2'], - }, - }, - }, - ], - }, - }, - }, - ], - }, - process: { - hits: { - total: { - value: 1, - relation: 'eq', - }, - max_score: 5, - hits: [], - }, - }, - }; - const bucket3: UncommonProcessBucket = { - key: '789', - hosts: { - buckets: [ - { - key: '789', - host: { - hits: { - total: 0, - max_score: 0, - hits: [ - { - _index: 'hit-9', - _type: 'type-9', - _id: 'id-9', - _score: 0, - _source: { - // @ts-expect-error ts doesn't like seeing the object written this way, but sometimes this is the data we get! - 'host.id': ['host-id-9'], - 'host.name': ['host-9'], - }, - }, - ], - }, - }, - }, - ], - }, - process: { - hits: { - total: { - value: 1, - relation: 'eq', - }, - max_score: 5, - hits: [], - }, - }, - }; - - test('will return a single host correctly', () => { - const hosts = getHosts(bucket1.hosts.buckets); - expect(hosts).toEqual([{ id: ['123'], name: ['host-1'] }]); - }); - - test('will return two hosts correctly', () => { - const hosts = getHosts(bucket2.hosts.buckets); - expect(hosts).toEqual([ - { id: ['123'], name: ['host-1'] }, - { id: ['345'], name: ['host-2'] }, - ]); - }); - - test('will return a dot notation host', () => { - const hosts = getHosts(bucket3.hosts.buckets); - expect(hosts).toEqual([{ id: ['789'], name: ['host-9'] }]); - }); - - test('will return no hosts when given an empty array', () => { - const hosts = getHosts([]); - expect(hosts).toEqual([]); - }); - }); - - describe('#formatUncommonProcessesData', () => { - const hit: UncommonProcessHit = { - _index: 'index-123', - _type: 'type-123', - _id: 'id-123', - _score: 10, - total: { - value: 100, - relation: 'eq', - }, - host: [ - { id: ['host-id-1'], name: ['host-name-1'] }, - { id: ['host-id-1'], name: ['host-name-1'] }, - ], - _source: { - '@timestamp': 'time', - process: { - name: ['process-1'], - title: ['title-1'], - }, - }, - cursor: 'cursor-1', - sort: [0], - }; - - test('it formats a uncommon process data with a source of name correctly', () => { - const fields: readonly string[] = ['process.name']; - const data = formatUncommonProcessesData(fields, hit, processFieldsMap); - const expected: UncommonProcessesEdges = { - cursor: { tiebreaker: null, value: 'cursor-1' }, - node: { - _id: 'id-123', - hosts: [ - { id: ['host-id-1'], name: ['host-name-1'] }, - { id: ['host-id-1'], name: ['host-name-1'] }, - ], - process: { - name: ['process-1'], - }, - instances: 100, - }, - }; - expect(data).toEqual(expected); - }); - - test('it formats a uncommon process data with a source of name and title correctly', () => { - const fields: readonly string[] = ['process.name', 'process.title']; - const data = formatUncommonProcessesData(fields, hit, processFieldsMap); - const expected: UncommonProcessesEdges = { - cursor: { tiebreaker: null, value: 'cursor-1' }, - node: { - _id: 'id-123', - hosts: [ - { id: ['host-id-1'], name: ['host-name-1'] }, - { id: ['host-id-1'], name: ['host-name-1'] }, - ], - instances: 100, - process: { - name: ['process-1'], - title: ['title-1'], - }, - }, - }; - expect(data).toEqual(expected); - }); - - test('it formats a uncommon process data without any data if fields is empty', () => { - const fields: readonly string[] = []; - const data = formatUncommonProcessesData(fields, hit, processFieldsMap); - const expected: UncommonProcessesEdges = { - cursor: { - tiebreaker: null, - value: '', - }, - node: { - _id: '', - hosts: [], - instances: 0, - process: {}, - }, - }; - expect(data).toEqual(expected); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/server/lib/uncommon_processes/elasticsearch_adapter.ts b/x-pack/plugins/security_solution/server/lib/uncommon_processes/elasticsearch_adapter.ts deleted file mode 100644 index 046823da7cb85..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/uncommon_processes/elasticsearch_adapter.ts +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { get, getOr } from 'lodash/fp'; - -import { UncommonProcessesData, UncommonProcessesEdges } from '../../graphql/types'; -import { mergeFieldsWithHit, inspectStringifyObject } from '../../utils/build_query'; -import { processFieldsMap, userFieldsMap } from '../ecs_fields'; -import { FrameworkAdapter, FrameworkRequest, RequestOptionsPaginated } from '../framework'; -import { HostHits, TermAggregation } from '../types'; -import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../common/constants'; -import { buildQuery } from './query.dsl'; -import { - UncommonProcessBucket, - UncommonProcessData, - UncommonProcessesAdapter, - UncommonProcessHit, -} from './types'; - -export class ElasticsearchUncommonProcessesAdapter implements UncommonProcessesAdapter { - constructor(private readonly framework: FrameworkAdapter) {} - - public async getUncommonProcesses( - request: FrameworkRequest, - options: RequestOptionsPaginated - ): Promise { - if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { - throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); - } - const dsl = buildQuery(options); - const response = await this.framework.callWithRequest( - request, - 'search', - dsl - ); - const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; - const totalCount = getOr(0, 'aggregations.process_count.value', response); - const buckets = getOr([], 'aggregations.group_by_process.buckets', response); - const hits = getHits(buckets); - - const uncommonProcessesEdges = hits.map((hit) => - formatUncommonProcessesData(options.fields, hit, { ...processFieldsMap, ...userFieldsMap }) - ); - - const fakeTotalCount = fakePossibleCount <= totalCount ? fakePossibleCount : totalCount; - const edges = uncommonProcessesEdges.splice(cursorStart, querySize - cursorStart); - const inspect = { - dsl: [inspectStringifyObject(dsl)], - response: [inspectStringifyObject(response)], - }; - - const showMorePagesIndicator = totalCount > fakeTotalCount; - return { - edges, - inspect, - pageInfo: { - activePage: activePage ? activePage : 0, - fakeTotalCount, - showMorePagesIndicator, - }, - totalCount, - }; - } -} - -export const getHits = (buckets: readonly UncommonProcessBucket[]): readonly UncommonProcessHit[] => - buckets.map((bucket: Readonly) => ({ - _id: bucket.process.hits.hits[0]._id, - _index: bucket.process.hits.hits[0]._index, - _type: bucket.process.hits.hits[0]._type, - _score: bucket.process.hits.hits[0]._score, - _source: bucket.process.hits.hits[0]._source, - sort: bucket.process.hits.hits[0].sort, - cursor: bucket.process.hits.hits[0].cursor, - total: bucket.process.hits.total, - host: getHosts(bucket.hosts.buckets), - })); - -export const getHosts = (buckets: ReadonlyArray<{ key: string; host: HostHits }>) => - buckets.map((bucket) => { - const source = get('host.hits.hits[0]._source', bucket); - return { - id: [bucket.key], - name: get('host.name', source), - }; - }); - -export const formatUncommonProcessesData = ( - fields: readonly string[], - hit: UncommonProcessHit, - fieldMap: Readonly> -): UncommonProcessesEdges => - fields.reduce( - (flattenedFields, fieldName) => { - flattenedFields.node._id = hit._id; - flattenedFields.node.instances = getOr(0, 'total.value', hit); - flattenedFields.node.hosts = hit.host; - if (hit.cursor) { - flattenedFields.cursor.value = hit.cursor; - } - return mergeFieldsWithHit(fieldName, flattenedFields, fieldMap, hit); - }, - { - node: { - _id: '', - instances: 0, - process: {}, - hosts: [], - }, - cursor: { - value: '', - tiebreaker: null, - }, - } - ); diff --git a/x-pack/plugins/security_solution/server/lib/uncommon_processes/index.ts b/x-pack/plugins/security_solution/server/lib/uncommon_processes/index.ts deleted file mode 100644 index 0ba0e90f391e1..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/uncommon_processes/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { UncommonProcessesData } from '../../graphql/types'; -import { FrameworkRequest, RequestOptionsPaginated } from '../framework'; -export * from './elasticsearch_adapter'; -import { UncommonProcessesAdapter } from './types'; - -export class UncommonProcesses { - constructor(private readonly adapter: UncommonProcessesAdapter) {} - - public async getUncommonProcesses( - req: FrameworkRequest, - options: RequestOptionsPaginated - ): Promise { - return this.adapter.getUncommonProcesses(req, options); - } -} diff --git a/x-pack/plugins/security_solution/server/lib/uncommon_processes/query.dsl.ts b/x-pack/plugins/security_solution/server/lib/uncommon_processes/query.dsl.ts deleted file mode 100644 index 4563c769cdc31..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/uncommon_processes/query.dsl.ts +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { createQueryFilterClauses } from '../../utils/build_query'; -import { reduceFields } from '../../utils/build_query/reduce_fields'; -import { hostFieldsMap, processFieldsMap, userFieldsMap } from '../ecs_fields'; -import { RequestOptionsPaginated } from '../framework'; - -export const buildQuery = ({ - defaultIndex, - fields, - filterQuery, - pagination: { querySize }, - sourceConfiguration: { - fields: { timestamp }, - }, - timerange: { from, to }, -}: RequestOptionsPaginated) => { - const processUserFields = reduceFields(fields, { ...processFieldsMap, ...userFieldsMap }); - const hostFields = reduceFields(fields, hostFieldsMap); - const filter = [ - ...createQueryFilterClauses(filterQuery), - { - range: { - [timestamp]: { - gte: from, - lte: to, - format: 'strict_date_optional_time', - }, - }, - }, - ]; - - const agg = { - process_count: { - cardinality: { - field: 'process.name', - }, - }, - }; - - const dslQuery = { - allowNoIndices: true, - index: defaultIndex, - ignoreUnavailable: true, - body: { - aggregations: { - ...agg, - group_by_process: { - terms: { - size: querySize, - field: 'process.name', - order: [ - { - host_count: 'asc', - }, - { - _count: 'asc', - }, - { - _key: 'asc', - }, - ], - }, - aggregations: { - process: { - top_hits: { - size: 1, - sort: [{ '@timestamp': { order: 'desc' } }], - _source: processUserFields, - }, - }, - host_count: { - cardinality: { - field: 'host.name', - }, - }, - hosts: { - terms: { - field: 'host.name', - }, - aggregations: { - host: { - top_hits: { - size: 1, - _source: hostFields, - }, - }, - }, - }, - }, - }, - }, - query: { - bool: { - should: [ - { - bool: { - filter: [ - { - term: { - 'agent.type': 'auditbeat', - }, - }, - { - term: { - 'event.module': 'auditd', - }, - }, - { - term: { - 'event.action': 'executed', - }, - }, - ], - }, - }, - { - bool: { - filter: [ - { - term: { - 'agent.type': 'auditbeat', - }, - }, - { - term: { - 'event.module': 'system', - }, - }, - { - term: { - 'event.dataset': 'process', - }, - }, - { - term: { - 'event.action': 'process_started', - }, - }, - ], - }, - }, - { - bool: { - filter: [ - { - term: { - 'agent.type': 'winlogbeat', - }, - }, - { - term: { - 'event.code': '4688', - }, - }, - ], - }, - }, - { - bool: { - filter: [ - { - term: { - 'winlog.event_id': 1, - }, - }, - { - term: { - 'winlog.channel': 'Microsoft-Windows-Sysmon/Operational', - }, - }, - ], - }, - }, - { - bool: { - filter: [ - { - term: { - 'event.type': 'process_start', - }, - }, - { - term: { - 'event.category': 'process', - }, - }, - ], - }, - }, - { - bool: { - filter: [ - { - term: { - 'event.category': 'process', - }, - }, - { - term: { - 'event.type': 'start', - }, - }, - ], - }, - }, - ], - minimum_should_match: 1, - filter, - }, - }, - }, - size: 0, - track_total_hits: false, - }; - - return dslQuery; -}; diff --git a/x-pack/plugins/security_solution/server/lib/uncommon_processes/types.ts b/x-pack/plugins/security_solution/server/lib/uncommon_processes/types.ts deleted file mode 100644 index dc60de5963a18..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/uncommon_processes/types.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { ProcessEcsFields, UncommonProcessesData } from '../../graphql/types'; -import { FrameworkRequest, RequestOptionsPaginated } from '../framework'; -import { Hit, Hits, HostHits, SearchHit, TotalHit } from '../types'; - -export interface UncommonProcessesAdapter { - getUncommonProcesses( - req: FrameworkRequest, - options: RequestOptionsPaginated - ): Promise; -} - -type StringOrNumber = string | number; -export interface UncommonProcessHit extends Hit { - total: TotalHit; - host: Array<{ - id: string[] | string | null | undefined; - name: string[] | string | null | undefined; - }>; - _source: { - '@timestamp': string; - process: ProcessEcsFields; - }; - cursor: string; - sort: StringOrNumber[]; -} - -export type ProcessHits = Hits; - -export interface UncommonProcessBucket { - key: string; - hosts: { - buckets: Array<{ key: string; host: HostHits }>; - }; - process: ProcessHits; -} - -export interface UncommonProcessData extends SearchHit { - sort: string[]; - aggregations: { - process_count: { - value: number; - }; - group_by_process: { - after_key: string; - buckets: UncommonProcessBucket[]; - }; - }; -} diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 0571c4878956f..22dbd623930c5 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -62,6 +62,7 @@ import { initUsageCollectors } from './usage'; import { AppRequestContext } from './types'; import { registerTrustedAppsRoutes } from './endpoint/routes/trusted_apps'; import { securitySolutionSearchStrategyProvider } from './search_strategy/security_solution'; +import { securitySolutionIndexFieldsProvider } from './search_strategy/index_fields'; import { securitySolutionTimelineSearchStrategyProvider } from './search_strategy/timeline'; export interface SetupPlugins { @@ -277,10 +278,16 @@ export class Plugin implements IPlugin { @@ -29,7 +24,7 @@ describe('Index Fields', () => { sortBy('name', [ { description: - 'Date/time when the event originated.\n\nThis is the date/time extracted from the event, typically representing when\nthe event was generated by the source.\n\nIf the event source has no original timestamp, this value is typically populated\nby the first time the event was received by the pipeline.\n\nRequired field for all events.', + 'Date/time when the event originated. This is the date/time extracted from the event, typically representing when the event was generated by the source. If the event source has no original timestamp, this value is typically populated by the first time the event was received by the pipeline. Required field for all events.', example: '2016-05-23T08:05:34.853Z', name: '@timestamp', type: 'date', @@ -37,41 +32,37 @@ describe('Index Fields', () => { aggregatable: true, category: 'base', indexes: ['auditbeat', 'filebeat', 'packetbeat'], + readFromDocValues: true, + esTypes: [], }, { description: 'Each document has an _id that uniquely identifies it', example: 'Y-6TfmcB0WOhS6qyMv3s', - footnote: '', - group: 1, - level: 'core', name: '_id', - required: true, type: 'string', searchable: true, aggregatable: false, - readFromDocValues: true, - category: '_id', + readFromDocValues: false, + category: 'base', indexes: ['auditbeat', 'filebeat', 'packetbeat'], + esTypes: [], }, { description: 'An index is like a ‘database’ in a relational database. It has a mapping which defines multiple types. An index is a logical namespace which maps to one or more primary shards and can have zero or more replica shards.', example: 'auditbeat-8.0.0-2019.02.19-000001', - footnote: '', - group: 1, - level: 'core', name: '_index', - required: true, type: 'string', searchable: true, aggregatable: true, - readFromDocValues: true, - category: '_index', + readFromDocValues: false, + category: 'base', indexes: ['auditbeat', 'filebeat', 'packetbeat'], + esTypes: [], }, { description: - 'Ephemeral identifier of this agent (if one exists).\n\nThis id normally changes across restarts, but `agent.id` does not.', + 'Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but `agent.id` does not.', example: '8a4f500f', name: 'agent.ephemeral_id', type: 'string', @@ -79,18 +70,24 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['auditbeat'], + readFromDocValues: false, + esTypes: [], }, { + description: + 'Deprecated - use agent.name or agent.id to identify an agent. Hostname of the agent. ', name: 'agent.hostname', searchable: true, type: 'string', aggregatable: true, category: 'agent', indexes: ['filebeat'], + readFromDocValues: false, + esTypes: [], }, { description: - 'Unique identifier of this agent (if one exists).\n\nExample: For Beats this would be beat.id.', + 'Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.', example: '8a4f500d', name: 'agent.id', type: 'string', @@ -98,10 +95,12 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['packetbeat'], + readFromDocValues: false, + esTypes: [], }, { description: - 'Custom name of the agent.\n\nThis is a name that can be given to an agent. This can be helpful if for example\ntwo Filebeat instances are running on the same host but a human readable separation\nis needed on which Filebeat instance data is coming from.\n\nIf no name is given, the name is often left empty.', + 'Custom name of the agent. This is a name that can be given to an agent. This can be helpful if for example two Filebeat instances are running on the same host but a human readable separation is needed on which Filebeat instance data is coming from. If no name is given, the name is often left empty.', example: 'foo', name: 'agent.name', type: 'string', @@ -109,10 +108,12 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['auditbeat', 'filebeat'], + readFromDocValues: false, + esTypes: [], }, { description: - 'Type of the agent.\n\nThe agent type stays always the same and should be given by the agent used.\nIn case of Filebeat the agent would always be Filebeat also if two Filebeat\ninstances are run on the same machine.', + 'Type of the agent. The agent type stays always the same and should be given by the agent used. In case of Filebeat the agent would always be Filebeat also if two Filebeat instances are run on the same machine.', example: 'filebeat', name: 'agent.type', type: 'string', @@ -120,6 +121,8 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['auditbeat', 'packetbeat'], + readFromDocValues: false, + esTypes: [], }, { description: 'Version of the agent.', @@ -130,6 +133,8 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['auditbeat', 'filebeat'], + readFromDocValues: false, + esTypes: [], }, ]) ); @@ -146,37 +151,31 @@ describe('Index Fields', () => { { description: 'Each document has an _id that uniquely identifies it', example: 'Y-6TfmcB0WOhS6qyMv3s', - footnote: '', - group: 1, - level: 'core', name: '_id', - required: true, type: 'string', searchable: true, aggregatable: false, - readFromDocValues: true, - category: '_id', + readFromDocValues: false, + category: 'base', indexes: ['auditbeat'], + esTypes: [], }, { description: 'An index is like a ‘database’ in a relational database. It has a mapping which defines multiple types. An index is a logical namespace which maps to one or more primary shards and can have zero or more replica shards.', example: 'auditbeat-8.0.0-2019.02.19-000001', - footnote: '', - group: 1, - level: 'core', name: '_index', - required: true, type: 'string', searchable: true, aggregatable: true, - readFromDocValues: true, - category: '_index', + readFromDocValues: false, + category: 'base', indexes: ['auditbeat'], + esTypes: [], }, { description: - 'Date/time when the event originated.\n\nThis is the date/time extracted from the event, typically representing when\nthe event was generated by the source.\n\nIf the event source has no original timestamp, this value is typically populated\nby the first time the event was received by the pipeline.\n\nRequired field for all events.', + 'Date/time when the event originated. This is the date/time extracted from the event, typically representing when the event was generated by the source. If the event source has no original timestamp, this value is typically populated by the first time the event was received by the pipeline. Required field for all events.', example: '2016-05-23T08:05:34.853Z', name: '@timestamp', type: 'date', @@ -184,10 +183,12 @@ describe('Index Fields', () => { aggregatable: true, category: 'base', indexes: ['auditbeat'], + readFromDocValues: true, + esTypes: [], }, { description: - 'Ephemeral identifier of this agent (if one exists).\n\nThis id normally changes across restarts, but `agent.id` does not.', + 'Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but `agent.id` does not.', example: '8a4f500f', name: 'agent.ephemeral_id', type: 'string', @@ -195,10 +196,12 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['auditbeat'], + readFromDocValues: false, + esTypes: [], }, { description: - 'Custom name of the agent.\n\nThis is a name that can be given to an agent. This can be helpful if for example\ntwo Filebeat instances are running on the same host but a human readable separation\nis needed on which Filebeat instance data is coming from.\n\nIf no name is given, the name is often left empty.', + 'Custom name of the agent. This is a name that can be given to an agent. This can be helpful if for example two Filebeat instances are running on the same host but a human readable separation is needed on which Filebeat instance data is coming from. If no name is given, the name is often left empty.', example: 'foo', name: 'agent.name', type: 'string', @@ -206,10 +209,12 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['auditbeat'], + readFromDocValues: false, + esTypes: [], }, { description: - 'Type of the agent.\n\nThe agent type stays always the same and should be given by the agent used.\nIn case of Filebeat the agent would always be Filebeat also if two Filebeat\ninstances are run on the same machine.', + 'Type of the agent. The agent type stays always the same and should be given by the agent used. In case of Filebeat the agent would always be Filebeat also if two Filebeat instances are run on the same machine.', example: 'filebeat', name: 'agent.type', type: 'string', @@ -217,6 +222,8 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['auditbeat'], + readFromDocValues: false, + esTypes: [], }, { description: 'Version of the agent.', @@ -227,41 +234,37 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['auditbeat'], + readFromDocValues: false, + esTypes: [], }, { description: 'Each document has an _id that uniquely identifies it', example: 'Y-6TfmcB0WOhS6qyMv3s', - footnote: '', - group: 1, - level: 'core', name: '_id', - required: true, type: 'string', searchable: true, aggregatable: false, - readFromDocValues: true, - category: '_id', + category: 'base', indexes: ['filebeat'], + readFromDocValues: false, + esTypes: [], }, { description: 'An index is like a ‘database’ in a relational database. It has a mapping which defines multiple types. An index is a logical namespace which maps to one or more primary shards and can have zero or more replica shards.', example: 'auditbeat-8.0.0-2019.02.19-000001', - footnote: '', - group: 1, - level: 'core', name: '_index', - required: true, type: 'string', searchable: true, aggregatable: true, - readFromDocValues: true, - category: '_index', + category: 'base', indexes: ['filebeat'], + readFromDocValues: false, + esTypes: [], }, { description: - 'Date/time when the event originated.\n\nThis is the date/time extracted from the event, typically representing when\nthe event was generated by the source.\n\nIf the event source has no original timestamp, this value is typically populated\nby the first time the event was received by the pipeline.\n\nRequired field for all events.', + 'Date/time when the event originated. This is the date/time extracted from the event, typically representing when the event was generated by the source. If the event source has no original timestamp, this value is typically populated by the first time the event was received by the pipeline. Required field for all events.', example: '2016-05-23T08:05:34.853Z', name: '@timestamp', type: 'date', @@ -269,18 +272,24 @@ describe('Index Fields', () => { aggregatable: true, category: 'base', indexes: ['filebeat'], + readFromDocValues: true, + esTypes: [], }, { + description: + 'Deprecated - use agent.name or agent.id to identify an agent. Hostname of the agent. ', name: 'agent.hostname', searchable: true, type: 'string', aggregatable: true, category: 'agent', indexes: ['filebeat'], + readFromDocValues: false, + esTypes: [], }, { description: - 'Custom name of the agent.\n\nThis is a name that can be given to an agent. This can be helpful if for example\ntwo Filebeat instances are running on the same host but a human readable separation\nis needed on which Filebeat instance data is coming from.\n\nIf no name is given, the name is often left empty.', + 'Custom name of the agent. This is a name that can be given to an agent. This can be helpful if for example two Filebeat instances are running on the same host but a human readable separation is needed on which Filebeat instance data is coming from. If no name is given, the name is often left empty.', example: 'foo', name: 'agent.name', type: 'string', @@ -288,6 +297,8 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['filebeat'], + readFromDocValues: false, + esTypes: [], }, { description: 'Version of the agent.', @@ -298,41 +309,37 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['filebeat'], + readFromDocValues: false, + esTypes: [], }, { description: 'Each document has an _id that uniquely identifies it', example: 'Y-6TfmcB0WOhS6qyMv3s', - footnote: '', - group: 1, - level: 'core', name: '_id', - required: true, type: 'string', searchable: true, aggregatable: false, - readFromDocValues: true, - category: '_id', + category: 'base', indexes: ['packetbeat'], + readFromDocValues: false, + esTypes: [], }, { description: 'An index is like a ‘database’ in a relational database. It has a mapping which defines multiple types. An index is a logical namespace which maps to one or more primary shards and can have zero or more replica shards.', example: 'auditbeat-8.0.0-2019.02.19-000001', - footnote: '', - group: 1, - level: 'core', name: '_index', - required: true, type: 'string', searchable: true, aggregatable: true, - readFromDocValues: true, - category: '_index', + category: 'base', indexes: ['packetbeat'], + readFromDocValues: false, + esTypes: [], }, { description: - 'Date/time when the event originated.\n\nThis is the date/time extracted from the event, typically representing when\nthe event was generated by the source.\n\nIf the event source has no original timestamp, this value is typically populated\nby the first time the event was received by the pipeline.\n\nRequired field for all events.', + 'Date/time when the event originated. This is the date/time extracted from the event, typically representing when the event was generated by the source. If the event source has no original timestamp, this value is typically populated by the first time the event was received by the pipeline. Required field for all events.', example: '2016-05-23T08:05:34.853Z', name: '@timestamp', type: 'date', @@ -340,10 +347,12 @@ describe('Index Fields', () => { aggregatable: true, category: 'base', indexes: ['packetbeat'], + readFromDocValues: true, + esTypes: [], }, { description: - 'Unique identifier of this agent (if one exists).\n\nExample: For Beats this would be beat.id.', + 'Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.', example: '8a4f500d', name: 'agent.id', type: 'string', @@ -351,10 +360,12 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['packetbeat'], + readFromDocValues: false, + esTypes: [], }, { description: - 'Type of the agent.\n\nThe agent type stays always the same and should be given by the agent used.\nIn case of Filebeat the agent would always be Filebeat also if two Filebeat\ninstances are run on the same machine.', + 'Type of the agent. The agent type stays always the same and should be given by the agent used. In case of Filebeat the agent would always be Filebeat also if two Filebeat instances are run on the same machine.', example: 'filebeat', name: 'agent.type', type: 'string', @@ -362,6 +373,8 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['packetbeat'], + readFromDocValues: false, + esTypes: [], }, ]); }); @@ -377,8 +390,9 @@ describe('Index Fields', () => { type: 'string', searchable: true, aggregatable: false, - category: '_id', + category: 'base', indexes: ['auditbeat'], + readFromDocValues: false, }, { description: @@ -388,12 +402,13 @@ describe('Index Fields', () => { type: 'string', searchable: true, aggregatable: true, - category: '_index', + category: 'base', indexes: ['auditbeat'], + readFromDocValues: false, }, { description: - 'Date/time when the event originated.\n\nThis is the date/time extracted from the event, typically representing when\nthe event was generated by the source.\n\nIf the event source has no original timestamp, this value is typically populated\nby the first time the event was received by the pipeline.\n\nRequired field for all events.', + 'Date/time when the event originated. This is the date/time extracted from the event, typically representing when the event was generated by the source. If the event source has no original timestamp, this value is typically populated by the first time the event was received by the pipeline. Required field for all events.', example: '2016-05-23T08:05:34.853Z', name: '@timestamp', type: 'date', @@ -401,10 +416,11 @@ describe('Index Fields', () => { aggregatable: true, category: 'base', indexes: ['auditbeat'], + readFromDocValues: true, }, { description: - 'Ephemeral identifier of this agent (if one exists).\n\nThis id normally changes across restarts, but `agent.id` does not.', + 'Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but `agent.id` does not.', example: '8a4f500f', name: 'agent.ephemeral_id', type: 'string', @@ -412,10 +428,11 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['auditbeat'], + readFromDocValues: false, }, { description: - 'Custom name of the agent.\n\nThis is a name that can be given to an agent. This can be helpful if for example\ntwo Filebeat instances are running on the same host but a human readable separation\nis needed on which Filebeat instance data is coming from.\n\nIf no name is given, the name is often left empty.', + 'Custom name of the agent. This is a name that can be given to an agent. This can be helpful if for example two Filebeat instances are running on the same host but a human readable separation is needed on which Filebeat instance data is coming from. If no name is given, the name is often left empty.', example: 'foo', name: 'agent.name', type: 'string', @@ -423,10 +440,11 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['auditbeat'], + readFromDocValues: false, }, { description: - 'Type of the agent.\n\nThe agent type stays always the same and should be given by the agent used.\nIn case of Filebeat the agent would always be Filebeat also if two Filebeat\ninstances are run on the same machine.', + 'Type of the agent. The agent type stays always the same and should be given by the agent used. In case of Filebeat the agent would always be Filebeat also if two Filebeat instances are run on the same machine.', example: 'filebeat', name: 'agent.type', type: 'string', @@ -434,6 +452,7 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['auditbeat'], + readFromDocValues: false, }, { description: 'Version of the agent.', @@ -444,6 +463,7 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['auditbeat'], + readFromDocValues: false, }, { description: 'Each document has an _id that uniquely identifies it', @@ -452,8 +472,9 @@ describe('Index Fields', () => { type: 'string', searchable: true, aggregatable: false, - category: '_id', + category: 'base', indexes: ['filebeat'], + readFromDocValues: false, }, { description: @@ -463,12 +484,13 @@ describe('Index Fields', () => { type: 'string', searchable: true, aggregatable: true, - category: '_index', + category: 'base', indexes: ['filebeat'], + readFromDocValues: false, }, { description: - 'Date/time when the event originated.\n\nThis is the date/time extracted from the event, typically representing when\nthe event was generated by the source.\n\nIf the event source has no original timestamp, this value is typically populated\nby the first time the event was received by the pipeline.\n\nRequired field for all events.', + 'Date/time when the event originated. This is the date/time extracted from the event, typically representing when the event was generated by the source. If the event source has no original timestamp, this value is typically populated by the first time the event was received by the pipeline. Required field for all events.', example: '2016-05-23T08:05:34.853Z', name: '@timestamp', type: 'date', @@ -476,6 +498,7 @@ describe('Index Fields', () => { aggregatable: true, category: 'base', indexes: ['filebeat'], + readFromDocValues: true, }, { name: 'agent.hostname', @@ -484,10 +507,11 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['filebeat'], + readFromDocValues: false, }, { description: - 'Custom name of the agent.\n\nThis is a name that can be given to an agent. This can be helpful if for example\ntwo Filebeat instances are running on the same host but a human readable separation\nis needed on which Filebeat instance data is coming from.\n\nIf no name is given, the name is often left empty.', + 'Custom name of the agent. This is a name that can be given to an agent. This can be helpful if for example two Filebeat instances are running on the same host but a human readable separation is needed on which Filebeat instance data is coming from. If no name is given, the name is often left empty.', example: 'foo', name: 'agent.name', type: 'string', @@ -495,6 +519,7 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['filebeat'], + readFromDocValues: false, }, { description: 'Version of the agent.', @@ -505,6 +530,7 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['filebeat'], + readFromDocValues: false, }, { description: 'Each document has an _id that uniquely identifies it', @@ -513,8 +539,9 @@ describe('Index Fields', () => { type: 'string', searchable: true, aggregatable: false, - category: '_id', + category: 'base', indexes: ['packetbeat'], + readFromDocValues: false, }, { description: @@ -524,12 +551,13 @@ describe('Index Fields', () => { type: 'string', searchable: true, aggregatable: true, - category: '_index', + category: 'base', indexes: ['packetbeat'], + readFromDocValues: false, }, { description: - 'Date/time when the event originated.\n\nThis is the date/time extracted from the event, typically representing when\nthe event was generated by the source.\n\nIf the event source has no original timestamp, this value is typically populated\nby the first time the event was received by the pipeline.\n\nRequired field for all events.', + 'Date/time when the event originated. This is the date/time extracted from the event, typically representing when the event was generated by the source. If the event source has no original timestamp, this value is typically populated by the first time the event was received by the pipeline. Required field for all events.', example: '2016-05-23T08:05:34.853Z', name: '@timestamp', type: 'date', @@ -537,10 +565,11 @@ describe('Index Fields', () => { aggregatable: true, category: 'base', indexes: ['packetbeat'], + readFromDocValues: true, }, { description: - 'Unique identifier of this agent (if one exists).\n\nExample: For Beats this would be beat.id.', + 'Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.', example: '8a4f500d', name: 'agent.id', type: 'string', @@ -548,10 +577,11 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['packetbeat'], + readFromDocValues: false, }, { description: - 'Type of the agent.\n\nThe agent type stays always the same and should be given by the agent used.\nIn case of Filebeat the agent would always be Filebeat also if two Filebeat\ninstances are run on the same machine.', + 'Type of the agent. The agent type stays always the same and should be given by the agent used. In case of Filebeat the agent would always be Filebeat also if two Filebeat instances are run on the same machine.', example: 'filebeat', name: 'agent.type', type: 'string', @@ -559,6 +589,7 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['packetbeat'], + readFromDocValues: false, }, ]); expect(fields).toEqual([ @@ -569,8 +600,9 @@ describe('Index Fields', () => { type: 'string', searchable: true, aggregatable: false, - category: '_id', + category: 'base', indexes: ['auditbeat', 'filebeat', 'packetbeat'], + readFromDocValues: false, }, { description: @@ -580,12 +612,13 @@ describe('Index Fields', () => { type: 'string', searchable: true, aggregatable: true, - category: '_index', + category: 'base', indexes: ['auditbeat', 'filebeat', 'packetbeat'], + readFromDocValues: false, }, { description: - 'Date/time when the event originated.\n\nThis is the date/time extracted from the event, typically representing when\nthe event was generated by the source.\n\nIf the event source has no original timestamp, this value is typically populated\nby the first time the event was received by the pipeline.\n\nRequired field for all events.', + 'Date/time when the event originated. This is the date/time extracted from the event, typically representing when the event was generated by the source. If the event source has no original timestamp, this value is typically populated by the first time the event was received by the pipeline. Required field for all events.', example: '2016-05-23T08:05:34.853Z', name: '@timestamp', type: 'date', @@ -593,10 +626,11 @@ describe('Index Fields', () => { aggregatable: true, category: 'base', indexes: ['auditbeat', 'filebeat', 'packetbeat'], + readFromDocValues: true, }, { description: - 'Ephemeral identifier of this agent (if one exists).\n\nThis id normally changes across restarts, but `agent.id` does not.', + 'Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but `agent.id` does not.', example: '8a4f500f', name: 'agent.ephemeral_id', type: 'string', @@ -604,10 +638,11 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['auditbeat'], + readFromDocValues: false, }, { description: - 'Custom name of the agent.\n\nThis is a name that can be given to an agent. This can be helpful if for example\ntwo Filebeat instances are running on the same host but a human readable separation\nis needed on which Filebeat instance data is coming from.\n\nIf no name is given, the name is often left empty.', + 'Custom name of the agent. This is a name that can be given to an agent. This can be helpful if for example two Filebeat instances are running on the same host but a human readable separation is needed on which Filebeat instance data is coming from. If no name is given, the name is often left empty.', example: 'foo', name: 'agent.name', type: 'string', @@ -615,10 +650,11 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['auditbeat', 'filebeat'], + readFromDocValues: false, }, { description: - 'Type of the agent.\n\nThe agent type stays always the same and should be given by the agent used.\nIn case of Filebeat the agent would always be Filebeat also if two Filebeat\ninstances are run on the same machine.', + 'Type of the agent. The agent type stays always the same and should be given by the agent used. In case of Filebeat the agent would always be Filebeat also if two Filebeat instances are run on the same machine.', example: 'filebeat', name: 'agent.type', type: 'string', @@ -626,6 +662,7 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['auditbeat', 'packetbeat'], + readFromDocValues: false, }, { description: 'Version of the agent.', @@ -636,6 +673,7 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['auditbeat', 'filebeat'], + readFromDocValues: false, }, { name: 'agent.hostname', @@ -644,10 +682,11 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['filebeat'], + readFromDocValues: false, }, { description: - 'Unique identifier of this agent (if one exists).\n\nExample: For Beats this would be beat.id.', + 'Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.', example: '8a4f500d', name: 'agent.id', type: 'string', @@ -655,6 +694,7 @@ describe('Index Fields', () => { aggregatable: true, category: 'agent', indexes: ['packetbeat'], + readFromDocValues: false, }, ]); }); @@ -669,22 +709,22 @@ describe('Index Fields', () => { type: 'string', searchable: true, aggregatable: false, + readFromDocValues: false, + esTypes: [], }, 0 ); expect(item).toEqual({ description: 'Each document has an _id that uniquely identifies it', example: 'Y-6TfmcB0WOhS6qyMv3s', - footnote: '', - group: 1, - level: 'core', name: '_id', - required: true, type: 'string', searchable: true, aggregatable: false, - category: '_id', + category: 'base', indexes: ['auditbeat'], + readFromDocValues: false, + esTypes: [], }); }); }); diff --git a/x-pack/plugins/security_solution/server/search_strategy/index_fields/index.ts b/x-pack/plugins/security_solution/server/search_strategy/index_fields/index.ts new file mode 100644 index 0000000000000..403a9425b221f --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/index_fields/index.ts @@ -0,0 +1,224 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import isEmpty from 'lodash/isEmpty'; +import { IndexPatternsFetcher, ISearchStrategy } from '../../../../../../src/plugins/data/server'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { FieldDescriptor } from '../../../../../../src/plugins/data/server/index_patterns'; +import { + IndexFieldsStrategyResponse, + IndexField, + IndexFieldsStrategyRequest, +} from '../../../common/search_strategy/index_fields'; + +import { fieldsBeat } from '../../utils/beat_schema/fields'; + +export const securitySolutionIndexFieldsProvider = (): ISearchStrategy< + IndexFieldsStrategyRequest, + IndexFieldsStrategyResponse +> => { + return { + search: async (context, request) => { + const { elasticsearch } = context.core; + const indexPatternsFetcher = new IndexPatternsFetcher( + elasticsearch.legacy.client.callAsCurrentUser + ); + const dedupeIndices = dedupeIndexName(request.indices); + + const responsesIndexFields = await Promise.all( + dedupeIndices + .map((index) => + indexPatternsFetcher.getFieldsForWildcard({ + pattern: index, + }) + ) + .map((p) => p.catch((e) => false)) + ); + let indexFields: IndexField[] = []; + + if (!request.onlyCheckIfIndicesExist) { + indexFields = await formatIndexFields( + responsesIndexFields.filter((rif) => rif !== false) as FieldDescriptor[][], + dedupeIndices + ); + } + + return Promise.resolve({ + indexFields, + indicesExist: dedupeIndices.filter((index, i) => responsesIndexFields[i] !== false), + rawResponse: { + timed_out: false, + took: -1, + _shards: { + total: -1, + successful: -1, + failed: -1, + skipped: -1, + }, + hits: { + total: -1, + max_score: -1, + hits: [ + { + _index: '', + _type: '', + _id: '', + _score: -1, + _source: null, + }, + ], + }, + }, + }); + }, + }; +}; + +export const dedupeIndexName = (indices: string[]) => + indices.reduce((acc, index) => { + if (index.trim() !== '' && index.trim() !== '_all' && !acc.includes(index.trim())) { + return [...acc, index]; + } + return acc; + }, []); + +const missingFields: FieldDescriptor[] = [ + { + name: '_id', + type: 'string', + searchable: true, + aggregatable: false, + readFromDocValues: false, + esTypes: [], + }, + { + name: '_index', + type: 'string', + searchable: true, + aggregatable: true, + readFromDocValues: false, + esTypes: [], + }, +]; + +/** + * Creates a single field item. + * + * This is a mutatious HOT CODE PATH function that will have array sizes up to 4.7 megs + * in size at a time calling this function repeatedly. This function should be as optimized as possible + * and should avoid any and all creation of new arrays, iterating over the arrays or performing + * any n^2 operations. + * @param indexesAlias The index alias + * @param index The index its self + * @param indexesAliasIdx The index within the alias + */ +export const createFieldItem = ( + indexesAlias: string[], + index: FieldDescriptor, + indexesAliasIdx: number +): IndexField => { + const alias = indexesAlias[indexesAliasIdx]; + return { + ...(fieldsBeat[index.name] ?? {}), + ...index, + indexes: [alias], + }; +}; + +/** + * This is a mutatious HOT CODE PATH function that will have array sizes up to 4.7 megs + * in size at a time when being called. This function should be as optimized as possible + * and should avoid any and all creation of new arrays, iterating over the arrays or performing + * any n^2 operations. The `.push`, and `forEach` operations are expected within this function + * to speed up performance. + * + * This intentionally waits for the next tick on the event loop to process as the large 4.7 megs + * has already consumed a lot of the event loop processing up to this function and we want to give + * I/O opportunity to occur by scheduling this on the next loop. + * @param responsesIndexFields The response index fields to loop over + * @param indexesAlias The index aliases such as filebeat-* + */ +export const formatFirstFields = async ( + responsesIndexFields: FieldDescriptor[][], + indexesAlias: string[] +): Promise => { + return new Promise((resolve) => { + setTimeout(() => { + resolve( + responsesIndexFields.reduce( + (accumulator: IndexField[], indexFields: FieldDescriptor[], indexesAliasIdx: number) => { + missingFields.forEach((index) => { + const item = createFieldItem(indexesAlias, index, indexesAliasIdx); + accumulator.push(item); + }); + indexFields.forEach((index) => { + const item = createFieldItem(indexesAlias, index, indexesAliasIdx); + accumulator.push(item); + }); + return accumulator; + }, + [] + ) + ); + }); + }); +}; + +/** + * This is a mutatious HOT CODE PATH function that will have array sizes up to 4.7 megs + * in size at a time when being called. This function should be as optimized as possible + * and should avoid any and all creation of new arrays, iterating over the arrays or performing + * any n^2 operations. The `.push`, and `forEach` operations are expected within this function + * to speed up performance. The "indexFieldNameHash" side effect hash avoids additional expensive n^2 + * look ups. + * + * This intentionally waits for the next tick on the event loop to process as the large 4.7 megs + * has already consumed a lot of the event loop processing up to this function and we want to give + * I/O opportunity to occur by scheduling this on the next loop. + * @param fields The index fields to create the secondary fields for + */ +export const formatSecondFields = async (fields: IndexField[]): Promise => { + return new Promise((resolve) => { + setTimeout(() => { + const indexFieldNameHash: Record = {}; + const reduced = fields.reduce((accumulator: IndexField[], indexfield: IndexField) => { + const alreadyExistingIndexField = indexFieldNameHash[indexfield.name]; + if (alreadyExistingIndexField != null) { + const existingIndexField = accumulator[alreadyExistingIndexField]; + if (isEmpty(accumulator[alreadyExistingIndexField].description)) { + accumulator[alreadyExistingIndexField].description = indexfield.description; + } + accumulator[alreadyExistingIndexField].indexes = Array.from( + new Set([...existingIndexField.indexes, ...indexfield.indexes]) + ); + return accumulator; + } + accumulator.push(indexfield); + indexFieldNameHash[indexfield.name] = accumulator.length - 1; + return accumulator; + }, []); + resolve(reduced); + }); + }); +}; + +/** + * Formats the index fields into a format the UI wants. + * + * NOTE: This will have array sizes up to 4.7 megs in size at a time when being called. + * This function should be as optimized as possible and should avoid any and all creation + * of new arrays, iterating over the arrays or performing any n^2 operations. + * @param responsesIndexFields The response index fields to format + * @param indexesAlias The index alias + */ +export const formatIndexFields = async ( + responsesIndexFields: FieldDescriptor[][], + indexesAlias: string[] +): Promise => { + const fields = await formatFirstFields(responsesIndexFields, indexesAlias); + const secondFields = await formatSecondFields(fields); + return secondFields; +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/index_fields/mock.ts b/x-pack/plugins/security_solution/server/search_strategy/index_fields/mock.ts new file mode 100644 index 0000000000000..efb992a868f65 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/index_fields/mock.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { FieldDescriptor } from '../../../../../../src/plugins/data/server/index_patterns'; + +export const mockAuditbeatIndexField: FieldDescriptor[] = [ + { + name: '@timestamp', + searchable: true, + type: 'date', + aggregatable: true, + readFromDocValues: true, + esTypes: [], + }, + { + name: 'agent.ephemeral_id', + searchable: true, + type: 'string', + aggregatable: true, + readFromDocValues: false, + esTypes: [], + }, + { + name: 'agent.name', + searchable: true, + type: 'string', + aggregatable: true, + readFromDocValues: false, + esTypes: [], + }, + { + name: 'agent.type', + searchable: true, + type: 'string', + aggregatable: true, + readFromDocValues: false, + esTypes: [], + }, + { + name: 'agent.version', + searchable: true, + type: 'string', + aggregatable: true, + readFromDocValues: false, + esTypes: [], + }, +]; + +export const mockFilebeatIndexField: FieldDescriptor[] = [ + { + name: '@timestamp', + searchable: true, + type: 'date', + aggregatable: true, + readFromDocValues: true, + esTypes: [], + }, + { + name: 'agent.hostname', + searchable: true, + type: 'string', + aggregatable: true, + readFromDocValues: false, + esTypes: [], + }, + { + name: 'agent.name', + searchable: true, + type: 'string', + aggregatable: true, + readFromDocValues: false, + esTypes: [], + }, + { + name: 'agent.version', + searchable: true, + type: 'string', + aggregatable: true, + readFromDocValues: false, + esTypes: [], + }, +]; + +export const mockPacketbeatIndexField: FieldDescriptor[] = [ + { + name: '@timestamp', + searchable: true, + type: 'date', + aggregatable: true, + readFromDocValues: true, + esTypes: [], + }, + { + name: 'agent.id', + searchable: true, + type: 'string', + aggregatable: true, + readFromDocValues: false, + esTypes: [], + }, + { + name: 'agent.type', + searchable: true, + type: 'string', + aggregatable: true, + readFromDocValues: false, + esTypes: [], + }, +]; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/dsl/query.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/dsl/query.dsl.ts index df300c85e300f..08b83b489485a 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/dsl/query.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/dsl/query.dsl.ts @@ -12,7 +12,7 @@ import { sourceFieldsMap, hostFieldsMap } from '../../../../../../../common/ecs/ import { createQueryFilterClauses } from '../../../../../../utils/build_query'; import { reduceFields } from '../../../../../../utils/build_query/reduce_fields'; -import { authenticationFields } from '../helpers'; +import { authenticationsFields } from '../helpers'; import { extendMap } from '../../../../../../../common/ecs/ecs_fields/extend_map'; export const auditdFieldsMap: Readonly> = { @@ -32,7 +32,7 @@ export const buildQuery = ({ defaultIndex, docValueFields, }: HostAuthenticationsRequestOptions) => { - const esFields = reduceFields(authenticationFields, { ...hostFieldsMap, ...sourceFieldsMap }); + const esFields = reduceFields(authenticationsFields, { ...hostFieldsMap, ...sourceFieldsMap }); const filter = [ ...createQueryFilterClauses(filterQuery), diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/helpers.ts index d61914fda7d06..ce8900a578102 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/helpers.ts @@ -15,7 +15,7 @@ import { StrategyResponseType, } from '../../../../../../common/search_strategy/security_solution'; -export const authenticationFields = [ +export const authenticationsFields = [ '_id', 'failures', 'successes', @@ -31,7 +31,7 @@ export const authenticationFields = [ ]; export const formatAuthenticationData = ( - fields: readonly string[] = authenticationFields, + fields: readonly string[] = authenticationsFields, hit: AuthenticationHit, fieldMap: Readonly> ): AuthenticationsEdges => diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/index.tsx b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/index.tsx index a43f53880587a..e09d8de7ba945 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/index.tsx +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/index.tsx @@ -20,7 +20,7 @@ import { import { inspectStringifyObject } from '../../../../../utils/build_query'; import { SecuritySolutionFactory } from '../../types'; import { auditdFieldsMap, buildQuery as buildAuthenticationQuery } from './dsl/query.dsl'; -import { authenticationFields, formatAuthenticationData, getHits } from './helpers'; +import { authenticationsFields, formatAuthenticationData, getHits } from './helpers'; export const authentications: SecuritySolutionFactory = { buildDsl: (options: HostAuthenticationsRequestOptions) => { @@ -40,7 +40,7 @@ export const authentications: SecuritySolutionFactory - formatAuthenticationData(authenticationFields, hit, auditdFieldsMap) + formatAuthenticationData(authenticationsFields, hit, auditdFieldsMap) ); const edges = authenticationEdges.splice(cursorStart, querySize - cursorStart); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts index edcba88a0cd89..44c55ab6e7c9d 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts @@ -5,13 +5,16 @@ */ import { hostsFactory } from '.'; -import { HostsQueries } from '../../../../../common/search_strategy'; +import { HostsQueries, HostsKpiQueries } from '../../../../../common/search_strategy'; import { allHosts } from './all'; import { hostDetails } from './details'; import { hostOverview } from './overview'; import { firstLastSeenHost } from './last_first_seen'; import { uncommonProcesses } from './uncommon_processes'; import { authentications } from './authentications'; +import { hostsKpiAuthentications } from './kpi/authentications'; +import { hostsKpiHosts } from './kpi/hosts'; +import { hostsKpiUniqueIps } from './kpi/unique_ips'; jest.mock('./all'); jest.mock('./details'); @@ -19,6 +22,9 @@ jest.mock('./overview'); jest.mock('./last_first_seen'); jest.mock('./uncommon_processes'); jest.mock('./authentications'); +jest.mock('./kpi/authentications'); +jest.mock('./kpi/hosts'); +jest.mock('./kpi/unique_ips'); describe('hostsFactory', () => { test('should include correct apis', () => { @@ -29,6 +35,9 @@ describe('hostsFactory', () => { [HostsQueries.firstLastSeen]: firstLastSeenHost, [HostsQueries.uncommonProcesses]: uncommonProcesses, [HostsQueries.authentications]: authentications, + [HostsKpiQueries.kpiAuthentications]: hostsKpiAuthentications, + [HostsKpiQueries.kpiHosts]: hostsKpiHosts, + [HostsKpiQueries.kpiUniqueIps]: hostsKpiUniqueIps, }; expect(hostsFactory).toEqual(expectedHostsFactory); }); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts index 85619cfec62ce..ad6a6182d331b 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts @@ -7,6 +7,7 @@ import { FactoryQueryTypes, HostsQueries, + HostsKpiQueries, } from '../../../../../common/search_strategy/security_solution'; import { SecuritySolutionFactory } from '../types'; @@ -16,12 +17,21 @@ import { hostOverview } from './overview'; import { firstLastSeenHost } from './last_first_seen'; import { uncommonProcesses } from './uncommon_processes'; import { authentications } from './authentications'; +import { hostsKpiAuthentications } from './kpi/authentications'; +import { hostsKpiHosts } from './kpi/hosts'; +import { hostsKpiUniqueIps } from './kpi/unique_ips'; -export const hostsFactory: Record> = { +export const hostsFactory: Record< + HostsQueries | HostsKpiQueries, + SecuritySolutionFactory +> = { [HostsQueries.details]: hostDetails, [HostsQueries.hosts]: allHosts, [HostsQueries.overview]: hostOverview, [HostsQueries.firstLastSeen]: firstLastSeenHost, [HostsQueries.uncommonProcesses]: uncommonProcesses, [HostsQueries.authentications]: authentications, + [HostsKpiQueries.kpiAuthentications]: hostsKpiAuthentications, + [HostsKpiQueries.kpiHosts]: hostsKpiHosts, + [HostsKpiQueries.kpiUniqueIps]: hostsKpiUniqueIps, }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/helpers.ts new file mode 100644 index 0000000000000..513e361b5be05 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/helpers.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + HostsKpiHistogram, + HostsKpiAuthenticationsHistogramCount, + HostsKpiHistogramData, +} from '../../../../../../../common/search_strategy'; + +export const formatAuthenticationsHistogramData = ( + data: Array> +): HostsKpiHistogramData[] | null => + data && data.length > 0 + ? data.map(({ key, count }) => ({ + x: key, + y: count.doc_count, + })) + : null; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/index.ts new file mode 100644 index 0000000000000..bafc9a3accc6e --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/index.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getOr } from 'lodash/fp'; + +import { IEsSearchResponse } from '../../../../../../../../../../src/plugins/data/common'; +import { + HostsKpiQueries, + HostsKpiAuthenticationsStrategyResponse, + HostsKpiAuthenticationsRequestOptions, +} from '../../../../../../../common/search_strategy/security_solution/hosts'; +import { inspectStringifyObject } from '../../../../../../utils/build_query'; +import { SecuritySolutionFactory } from '../../../types'; +import { buildHostsKpiAuthenticationsQuery } from './query.hosts_kpi_authentications.dsl'; +import { formatAuthenticationsHistogramData } from './helpers'; + +export const hostsKpiAuthentications: SecuritySolutionFactory = { + buildDsl: (options: HostsKpiAuthenticationsRequestOptions) => + buildHostsKpiAuthenticationsQuery(options), + parse: async ( + options: HostsKpiAuthenticationsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const inspect = { + dsl: [inspectStringifyObject(buildHostsKpiAuthenticationsQuery(options))], + }; + + const authenticationsSuccessHistogram = getOr( + null, + 'aggregations.authentication_success_histogram.buckets', + response.rawResponse + ); + const authenticationsFailureHistogram = getOr( + null, + 'aggregations.authentication_failure_histogram.buckets', + response.rawResponse + ); + + return { + ...response, + inspect, + authenticationsSuccess: getOr( + null, + 'aggregations.authentication_success.doc_count', + response.rawResponse + ), + authenticationsSuccessHistogram: formatAuthenticationsHistogramData( + authenticationsSuccessHistogram + ), + authenticationsFailure: getOr( + null, + 'aggregations.authentication_failure.doc_count', + response.rawResponse + ), + authenticationsFailureHistogram: formatAuthenticationsHistogramData( + authenticationsFailureHistogram + ), + }; + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/query.hosts_kpi_authentications.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/query.hosts_kpi_authentications.dsl.ts new file mode 100644 index 0000000000000..8da5f7f95c5d1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/query.hosts_kpi_authentications.dsl.ts @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { HostsKpiAuthenticationsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/hosts'; +import { createQueryFilterClauses } from '../../../../../../utils/build_query'; + +export const buildHostsKpiAuthenticationsQuery = ({ + filterQuery, + timerange: { from, to }, + defaultIndex, +}: HostsKpiAuthenticationsRequestOptions) => { + const filter = [ + ...createQueryFilterClauses(filterQuery), + { + bool: { + filter: [ + { + term: { + 'event.category': 'authentication', + }, + }, + ], + }, + }, + { + range: { + '@timestamp': { + gte: from, + lte: to, + format: 'strict_date_optional_time', + }, + }, + }, + ]; + + const dslQuery = { + index: defaultIndex, + allowNoIndices: true, + ignoreUnavailable: true, + body: { + aggs: { + authentication_success: { + filter: { + term: { + 'event.outcome': 'success', + }, + }, + }, + authentication_success_histogram: { + auto_date_histogram: { + field: '@timestamp', + buckets: '6', + }, + aggs: { + count: { + filter: { + term: { + 'event.outcome': 'success', + }, + }, + }, + }, + }, + authentication_failure: { + filter: { + term: { + 'event.outcome': 'failure', + }, + }, + }, + authentication_failure_histogram: { + auto_date_histogram: { + field: '@timestamp', + buckets: '6', + }, + aggs: { + count: { + filter: { + term: { + 'event.outcome': 'failure', + }, + }, + }, + }, + }, + }, + query: { + bool: { + filter, + }, + }, + size: 0, + track_total_hits: false, + }, + }; + + return dslQuery; +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/common/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/common/index.ts new file mode 100644 index 0000000000000..080ef05c99136 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/common/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + HostsKpiHistogram, + HostsKpiGeneralHistogramCount, + HostsKpiHistogramData, +} from '../../../../../../../common/search_strategy'; + +export const formatGeneralHistogramData = ( + data: Array> +): HostsKpiHistogramData[] | null => + data && data.length > 0 + ? data.map(({ key, count }) => ({ + x: key, + y: count.value, + })) + : null; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/index.ts new file mode 100644 index 0000000000000..6d91ebf09895e --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/index.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getOr } from 'lodash/fp'; + +import { IEsSearchResponse } from '../../../../../../../../../../src/plugins/data/common'; +import { + HostsKpiQueries, + HostsKpiHostsStrategyResponse, + HostsKpiHostsRequestOptions, +} from '../../../../../../../common/search_strategy/security_solution/hosts'; +import { inspectStringifyObject } from '../../../../../../utils/build_query'; +import { SecuritySolutionFactory } from '../../../types'; +import { buildHostsKpiHostsQuery } from './query.hosts_kpi_hosts.dsl'; +import { formatGeneralHistogramData } from '../common'; + +export const hostsKpiHosts: SecuritySolutionFactory = { + buildDsl: (options: HostsKpiHostsRequestOptions) => buildHostsKpiHostsQuery(options), + parse: async ( + options: HostsKpiHostsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const inspect = { + dsl: [inspectStringifyObject(buildHostsKpiHostsQuery(options))], + }; + + const hostsHistogram = getOr( + null, + 'aggregations.hosts_histogram.buckets', + response.rawResponse + ); + return { + ...response, + inspect, + hosts: getOr(null, 'aggregations.hosts.value', response.rawResponse), + hostsHistogram: formatGeneralHistogramData(hostsHistogram), + }; + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/query.hosts_kpi_hosts.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/query.hosts_kpi_hosts.dsl.ts new file mode 100644 index 0000000000000..704743cc434ed --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/query.hosts_kpi_hosts.dsl.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { HostsKpiHostsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/hosts'; +import { createQueryFilterClauses } from '../../../../../../utils/build_query'; + +export const buildHostsKpiHostsQuery = ({ + filterQuery, + timerange: { from, to }, + defaultIndex, +}: HostsKpiHostsRequestOptions) => { + const filter = [ + ...createQueryFilterClauses(filterQuery), + { + range: { + '@timestamp': { + gte: from, + lte: to, + format: 'strict_date_optional_time', + }, + }, + }, + ]; + + const dslQuery = { + index: defaultIndex, + allowNoIndices: true, + ignoreUnavailable: true, + body: { + aggregations: { + hosts: { + cardinality: { + field: 'host.name', + }, + }, + hosts_histogram: { + auto_date_histogram: { + field: '@timestamp', + buckets: '6', + }, + aggs: { + count: { + cardinality: { + field: 'host.name', + }, + }, + }, + }, + }, + query: { + bool: { + filter, + }, + }, + size: 0, + track_total_hits: false, + }, + }; + + return dslQuery; +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/index.ts new file mode 100644 index 0000000000000..f4793ecd53f8f --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './authentications'; +export * from './common'; +export * from './hosts'; +export * from './unique_ips'; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/index.ts new file mode 100644 index 0000000000000..2f890e6fdacca --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/index.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getOr } from 'lodash/fp'; + +import { IEsSearchResponse } from '../../../../../../../../../../src/plugins/data/common'; +import { + HostsKpiQueries, + HostsKpiUniqueIpsStrategyResponse, + HostsKpiUniqueIpsRequestOptions, +} from '../../../../../../../common/search_strategy/security_solution/hosts'; +import { inspectStringifyObject } from '../../../../../../utils/build_query'; +import { SecuritySolutionFactory } from '../../../types'; +import { buildHostsKpiUniqueIpsQuery } from './query.hosts_kpi_unique_ips.dsl'; +import { formatGeneralHistogramData } from '../common'; + +export const hostsKpiUniqueIps: SecuritySolutionFactory = { + buildDsl: (options: HostsKpiUniqueIpsRequestOptions) => buildHostsKpiUniqueIpsQuery(options), + parse: async ( + options: HostsKpiUniqueIpsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const inspect = { + dsl: [inspectStringifyObject(buildHostsKpiUniqueIpsQuery(options))], + }; + + const uniqueSourceIpsHistogram = getOr( + null, + 'aggregations.unique_source_ips_histogram.buckets', + response.rawResponse + ); + + const uniqueDestinationIpsHistogram = getOr( + null, + 'aggregations.unique_destination_ips_histogram.buckets', + response.rawResponse + ); + + return { + ...response, + inspect, + uniqueSourceIps: getOr(null, 'aggregations.unique_source_ips.value', response.rawResponse), + uniqueSourceIpsHistogram: formatGeneralHistogramData(uniqueSourceIpsHistogram), + uniqueDestinationIps: getOr( + null, + 'aggregations.unique_destination_ips.value', + response.rawResponse + ), + uniqueDestinationIpsHistogram: formatGeneralHistogramData(uniqueDestinationIpsHistogram), + }; + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/query.hosts_kpi_unique_ips.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/query.hosts_kpi_unique_ips.dsl.ts new file mode 100644 index 0000000000000..618c6cb51f666 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/query.hosts_kpi_unique_ips.dsl.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { HostsKpiUniqueIpsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/hosts'; +import { createQueryFilterClauses } from '../../../../../../utils/build_query'; + +export const buildHostsKpiUniqueIpsQuery = ({ + filterQuery, + timerange: { from, to }, + defaultIndex, +}: HostsKpiUniqueIpsRequestOptions) => { + const filter = [ + ...createQueryFilterClauses(filterQuery), + { + range: { + '@timestamp': { + gte: from, + lte: to, + format: 'strict_date_optional_time', + }, + }, + }, + ]; + + const dslQuery = { + index: defaultIndex, + allowNoIndices: true, + ignoreUnavailable: true, + body: { + aggregations: { + unique_source_ips: { + cardinality: { + field: 'source.ip', + }, + }, + unique_source_ips_histogram: { + auto_date_histogram: { + field: '@timestamp', + buckets: '6', + }, + aggs: { + count: { + cardinality: { + field: 'source.ip', + }, + }, + }, + }, + unique_destination_ips: { + cardinality: { + field: 'destination.ip', + }, + }, + unique_destination_ips_histogram: { + auto_date_histogram: { + field: '@timestamp', + buckets: '6', + }, + aggs: { + count: { + cardinality: { + field: 'destination.ip', + }, + }, + }, + }, + }, + query: { + bool: { + filter, + }, + }, + size: 0, + track_total_hits: false, + }, + }; + + return dslQuery; +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/index.ts index 7a28c983ec466..61c228a5fd164 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/index.ts @@ -8,7 +8,7 @@ import { get, getOr } from 'lodash/fp'; import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; import { - HostOverviewStrategyResponse, + HostsOverviewStrategyResponse, HostsQueries, HostOverviewRequestOptions, OverviewHostHit, @@ -22,7 +22,7 @@ export const hostOverview: SecuritySolutionFactory = { parse: async ( options: HostOverviewRequestOptions, response: IEsSearchResponse - ): Promise => { + ): Promise => { const aggregations: OverviewHostHit = get('aggregations', response.rawResponse) || {}; const inspect = { dsl: [inspectStringifyObject(buildOverviewHostQuery(options))], diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/helpers.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/helpers.test.ts index 096ca570ae852..a6f44c78e5cc4 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/helpers.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/helpers.test.ts @@ -7,8 +7,8 @@ import { processFieldsMap } from '../../../../../../common/ecs/ecs_fields'; import { - UncommonProcessesEdges, - UncommonProcessHit, + HostsUncommonProcessesEdges, + HostsUncommonProcessHit, } from '../../../../../../common/search_strategy'; import { formatUncommonProcessesData, getHosts, UncommonProcessBucket } from './helpers'; @@ -183,7 +183,7 @@ describe('helpers', () => { }); describe('#formatUncommonProcessesData', () => { - const hit: UncommonProcessHit = { + const hit: HostsUncommonProcessHit = { _index: 'index-123', _type: 'type-123', _id: 'id-123', @@ -210,7 +210,7 @@ describe('helpers', () => { test('it formats a uncommon process data with a source of name correctly', () => { const fields: readonly string[] = ['process.name']; const data = formatUncommonProcessesData(fields, hit, processFieldsMap); - const expected: UncommonProcessesEdges = { + const expected: HostsUncommonProcessesEdges = { cursor: { tiebreaker: null, value: 'cursor-1' }, node: { _id: 'id-123', @@ -230,7 +230,7 @@ describe('helpers', () => { test('it formats a uncommon process data with a source of name and title correctly', () => { const fields: readonly string[] = ['process.name', 'process.title']; const data = formatUncommonProcessesData(fields, hit, processFieldsMap); - const expected: UncommonProcessesEdges = { + const expected: HostsUncommonProcessesEdges = { cursor: { tiebreaker: null, value: 'cursor-1' }, node: { _id: 'id-123', @@ -251,7 +251,7 @@ describe('helpers', () => { test('it formats a uncommon process data without any data if fields is empty', () => { const fields: readonly string[] = []; const data = formatUncommonProcessesData(fields, hit, processFieldsMap); - const expected: UncommonProcessesEdges = { + const expected: HostsUncommonProcessesEdges = { cursor: { tiebreaker: null, value: '', diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/helpers.ts index 5c3d76175b7e4..20b3f5b05bc87 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/helpers.ts @@ -9,8 +9,8 @@ import { set } from '@elastic/safer-lodash-set/fp'; import { mergeFieldsWithHit } from '../../../../../utils/build_query'; import { ProcessHits, - UncommonProcessesEdges, - UncommonProcessHit, + HostsUncommonProcessesEdges, + HostsUncommonProcessHit, } from '../../../../../../common/search_strategy/security_solution/hosts/uncommon_processes'; import { toArray } from '../../../../helpers/to_array'; import { HostHits } from '../../../../../../common/search_strategy'; @@ -25,7 +25,9 @@ export const uncommonProcessesFields = [ 'hosts.name', ]; -export const getHits = (buckets: readonly UncommonProcessBucket[]): readonly UncommonProcessHit[] => +export const getHits = ( + buckets: readonly UncommonProcessBucket[] +): readonly HostsUncommonProcessHit[] => buckets.map((bucket: Readonly) => ({ _id: bucket.process.hits.hits[0]._id, _index: bucket.process.hits.hits[0]._index, @@ -57,10 +59,10 @@ export const getHosts = (buckets: ReadonlyArray<{ key: string; host: HostHits }> export const formatUncommonProcessesData = ( fields: readonly string[], - hit: UncommonProcessHit, + hit: HostsUncommonProcessHit, fieldMap: Readonly> -): UncommonProcessesEdges => - fields.reduce( +): HostsUncommonProcessesEdges => + fields.reduce( (flattenedFields, fieldName) => { flattenedFields.node._id = hit._id; flattenedFields.node.instances = getOr(0, 'total.value', hit); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/index.test.ts index a5fa9b459d1bf..5016c8cc38ce4 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/index.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/index.test.ts @@ -6,7 +6,7 @@ import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants'; -import { HostUncommonProcessesRequestOptions } from '../../../../../../common/search_strategy/security_solution'; +import { HostsUncommonProcessesRequestOptions } from '../../../../../../common/search_strategy/security_solution'; import * as buildQuery from './dsl/query.dsl'; import { uncommonProcesses } from '.'; import { @@ -35,7 +35,7 @@ describe('uncommonProcesses search strategy', () => { ...mockOptions.pagination, querySize: DEFAULT_MAX_TABLE_QUERY_SIZE, }, - } as HostUncommonProcessesRequestOptions; + } as HostsUncommonProcessesRequestOptions; expect(() => { uncommonProcesses.buildDsl(overSizeOptions); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/index.ts index 5682e63b50ed0..add2cdb76628a 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/index.ts @@ -12,8 +12,8 @@ import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants import { HostsQueries } from '../../../../../../common/search_strategy/security_solution'; import { processFieldsMap, userFieldsMap } from '../../../../../../common/ecs/ecs_fields'; import { - HostUncommonProcessesRequestOptions, - HostUncommonProcessesStrategyResponse, + HostsUncommonProcessesRequestOptions, + HostsUncommonProcessesStrategyResponse, } from '../../../../../../common/search_strategy/security_solution/hosts/uncommon_processes'; import { inspectStringifyObject } from '../../../../../utils/build_query'; @@ -23,16 +23,16 @@ import { buildQuery } from './dsl/query.dsl'; import { formatUncommonProcessesData, getHits, uncommonProcessesFields } from './helpers'; export const uncommonProcesses: SecuritySolutionFactory = { - buildDsl: (options: HostUncommonProcessesRequestOptions) => { + buildDsl: (options: HostsUncommonProcessesRequestOptions) => { if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); } return buildQuery(options); }, parse: async ( - options: HostUncommonProcessesRequestOptions, + options: HostsUncommonProcessesRequestOptions, response: IEsSearchResponse - ): Promise => { + ): Promise => { const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; const totalCount = getOr(0, 'aggregations.process_count.value', response.rawResponse); const buckets = getOr([], 'aggregations.group_by_process.buckets', response.rawResponse); diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.ts index edff22766cc54..b2ce57d87ae6d 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.ts @@ -19,6 +19,7 @@ export const formatTimelineData = ( flattenedFields.node._id = hit._id; flattenedFields.node._index = hit._index; flattenedFields.node.ecs._id = hit._id; + flattenedFields.node.ecs.timestamp = hit._source['@timestamp']; flattenedFields.node.ecs._index = hit._index; if (hit.sort && hit.sort.length > 1) { flattenedFields.cursor.value = hit.sort[0]; diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/details/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/details/helpers.ts index 3b0935db9a5d6..2dd406ffaa450 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/details/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/details/helpers.ts @@ -7,7 +7,8 @@ import { get, isEmpty, isNumber, isObject, isString } from 'lodash/fp'; import { TimelineEventsDetailsItem } from '../../../../../../common/search_strategy/timeline'; -import { baseCategoryFields } from '../../../../../utils/beat_schema/8.0.0'; + +export const baseCategoryFields = ['@timestamp', 'labels', 'message', 'tags']; export const getFieldCategory = (field: string): string => { const fieldCategory = field.split('.')[0]; diff --git a/x-pack/plugins/security_solution/server/utils/beat_schema/8.0.0/auditbeat.ts b/x-pack/plugins/security_solution/server/utils/beat_schema/8.0.0/auditbeat.ts deleted file mode 100644 index 76c865679dd05..0000000000000 --- a/x-pack/plugins/security_solution/server/utils/beat_schema/8.0.0/auditbeat.ts +++ /dev/null @@ -1,7902 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -/** - * An instance of the unmodified schema exported from auditbeat-8.0.0-SNAPSHOT-darwin-x86_64.tar.gz - * - */ - -import { Schema } from '../type'; - -export const auditbeatSchema: Schema = [ - { - key: 'ecs', - title: 'ECS', - description: 'ECS Fields.', - fields: [ - { - name: '@timestamp', - level: 'core', - required: true, - type: 'date', - description: - 'Date/time when the event originated.\n\nThis is the date/time extracted from the event, typically representing when\nthe event was generated by the source.\n\nIf the event source has no original timestamp, this value is typically populated\nby the first time the event was received by the pipeline.\n\nRequired field for all events.', - example: '2016-05-23T08:05:34.853Z', - }, - { - name: 'labels', - level: 'core', - type: 'object', - object_type: 'keyword', - description: - 'Custom key/value pairs.\n\nCan be used to add meta information to events. Should not contain nested objects.\nAll values are stored as keyword.\n\nExample: `docker` and `k8s` labels.', - example: '{"application": "foo-bar", "env": "production"}', - }, - { - name: 'message', - level: 'core', - type: 'text', - description: - 'For log events the message field contains the log message, optimized\nfor viewing in a log viewer.\n\nFor structured logs without an original message field, other fields can be concatenated\nto form a human-readable summary of the event.\n\nIf multiple messages exist, they can be combined into one message.', - example: 'Hello World', - }, - { - name: 'tags', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'List of keywords used to tag each event.', - example: '["production", "env2"]', - }, - { - name: 'agent', - title: 'Agent', - group: 2, - description: - 'The agent fields contain the data about the software entity, if\nany, that collects, detects, or observes events on a host, or takes measurements\non a host.\n\nExamples include Beats. Agents may also run on observers. ECS agent.* fields\nshall be populated with details of the agent running on the host or observer\nwhere the event happened or the measurement was taken.', - footnote: - 'Examples: In the case of Beats for logs, the agent.name is filebeat.\nFor APM, it is the agent running in the app/service. The agent information does\nnot change if data is sent through queuing systems like Kafka, Redis, or processing\nsystems such as Logstash or APM Server.', - type: 'group', - fields: [ - { - name: 'ephemeral_id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Ephemeral identifier of this agent (if one exists).\n\nThis id normally changes across restarts, but `agent.id` does not.', - example: '8a4f500f', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique identifier of this agent (if one exists).\n\nExample: For Beats this would be beat.id.', - example: '8a4f500d', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Custom name of the agent.\n\nThis is a name that can be given to an agent. This can be helpful if for example\ntwo Filebeat instances are running on the same host but a human readable separation\nis needed on which Filebeat instance data is coming from.\n\nIf no name is given, the name is often left empty.', - example: 'foo', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Type of the agent.\n\nThe agent type stays always the same and should be given by the agent used.\nIn case of Filebeat the agent would always be Filebeat also if two Filebeat\ninstances are run on the same machine.', - example: 'filebeat', - }, - { - name: 'version', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Version of the agent.', - example: '6.0.0-rc2', - }, - ], - }, - { - name: 'as', - title: 'Autonomous System', - group: 2, - description: - 'An autonomous system (AS) is a collection of connected Internet Protocol\n(IP) routing prefixes under the control of one or more network operators on\nbehalf of a single administrative entity or domain that presents a common, clearly\ndefined routing policy to the internet.', - type: 'group', - fields: [ - { - name: 'number', - level: 'extended', - type: 'long', - description: - 'Unique number allocated to the autonomous system. The autonomous\nsystem number (ASN) uniquely identifies each network on the Internet.', - example: 15169, - }, - { - name: 'organization.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Organization name.', - example: 'Google LLC', - }, - ], - }, - { - name: 'client', - title: 'Client', - group: 2, - description: - 'A client is defined as the initiator of a network connection for\nevents regarding sessions, connections, or bidirectional flow records.\n\nFor TCP events, the client is the initiator of the TCP connection that sends\nthe SYN packet(s). For other protocols, the client is generally the initiator\nor requestor in the network transaction. Some systems use the term "originator"\nto refer the client in TCP connections. The client fields describe details about\nthe system acting as the client in the network event. Client fields are usually\npopulated in conjunction with server fields. Client fields are generally not\npopulated for packet-level events.\n\nClient / server representations can add semantic context to an exchange, which\nis helpful to visualize the data in certain situations. If your context falls\nin that category, you should still ensure that source and destination are filled\nappropriately.', - type: 'group', - fields: [ - { - name: 'address', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Some event client addresses are defined ambiguously. The event\nwill sometimes list an IP, a domain or a unix socket. You should always store\nthe raw address in the `.address` field.\n\nThen it should be duplicated to `.ip` or `.domain`, depending on which one\nit is.', - }, - { - name: 'as.number', - level: 'extended', - type: 'long', - description: - 'Unique number allocated to the autonomous system. The autonomous\nsystem number (ASN) uniquely identifies each network on the Internet.', - example: 15169, - }, - { - name: 'as.organization.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Organization name.', - example: 'Google LLC', - }, - { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - description: 'Bytes sent from the client to the server.', - example: 184, - }, - { - name: 'domain', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Client domain.', - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: - 'IP address of the client.\n\nCan be one or multiple IPv4 or IPv6 addresses.', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'MAC address of the client.', - }, - { - name: 'nat.ip', - level: 'extended', - type: 'ip', - description: - 'Translated IP of source based NAT sessions (e.g. internal client\nto internet).\n\nTypically connections traversing load balancers, firewalls, or routers.', - }, - { - name: 'nat.port', - level: 'extended', - type: 'long', - format: 'string', - description: - 'Translated port of source based NAT sessions (e.g. internal client\nto internet).\n\nTypically connections traversing load balancers, firewalls, or routers.', - }, - { - name: 'packets', - level: 'core', - type: 'long', - description: 'Packets sent from the client to the server.', - example: 12, - }, - { - name: 'port', - level: 'core', - type: 'long', - format: 'string', - description: 'Port of the client.', - }, - { - name: 'registered_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The highest registered client domain, stripped of the subdomain.\n\nFor example, the registered domain for "foo.google.com" is "google.com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last two labels will not work well for TLDs such as "co.uk".', - example: 'google.com', - }, - { - name: 'top_level_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The effective top level domain (eTLD), also known as the domain\nsuffix, is the last part of the domain name. For example, the top level domain\nfor google.com is "com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last label will not work well for effective TLDs such as "co.uk".', - example: 'co.uk', - }, - { - name: 'user.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the user is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.email', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'User email address.', - }, - { - name: 'user.full_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'user.group.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.group.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'user.group.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - { - name: 'user.hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'user.id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifiers of the user.', - }, - { - name: 'user.name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'cloud', - title: 'Cloud', - group: 2, - description: 'Fields related to the cloud or infrastructure the events are coming\nfrom.', - footnote: - 'Examples: If Metricbeat is running on an EC2 host and fetches data\nfrom its host, the cloud info contains the data about this machine. If Metricbeat\nruns on a remote machine outside the cloud and fetches data from a service running\nin the cloud, the field contains cloud data from the machine the service is\nrunning on.', - type: 'group', - fields: [ - { - name: 'account.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The cloud account or organization id used to identify different\nentities in a multi-tenant environment.\n\nExamples: AWS account id, Google Cloud ORG Id, or other unique identifier.', - example: 666777888999, - }, - { - name: 'availability_zone', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Availability zone in which this host is running.', - example: 'us-east-1c', - }, - { - name: 'instance.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Instance ID of the host machine.', - example: 'i-1234567890abcdef0', - }, - { - name: 'instance.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Instance name of the host machine.', - }, - { - name: 'machine.type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Machine type of the host machine.', - example: 't2.medium', - }, - { - name: 'provider', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the cloud provider. Example values are aws, azure, gcp,\nor digitalocean.', - example: 'aws', - }, - { - name: 'region', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Region in which this host is running.', - example: 'us-east-1', - }, - ], - }, - { - name: 'code_signature', - title: 'Code Signature', - group: 2, - description: 'These fields contain information about binary code signatures.', - type: 'group', - fields: [ - { - name: 'exists', - level: 'core', - type: 'boolean', - description: 'Boolean to capture if a signature is present.', - example: 'true', - default_field: false, - }, - { - name: 'status', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Additional information about the certificate status.\n\nThis is useful for logging cryptographic errors with the certificate validity\nor trust status. Leave unpopulated if the validity or trust of the certificate\nwas unchecked.', - example: 'ERROR_UNTRUSTED_ROOT', - default_field: false, - }, - { - name: 'subject_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Subject name of the code signer', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'trusted', - level: 'extended', - type: 'boolean', - description: - 'Stores the trust status of the certificate chain.\n\nValidating the trust of the certificate chain may be complicated, and this\nfield should only be populated by tools that actively check the status.', - example: 'true', - default_field: false, - }, - { - name: 'valid', - level: 'extended', - type: 'boolean', - description: - 'Boolean to capture if the digital signature is verified against\nthe binary content.\n\nLeave unpopulated if a certificate was unchecked.', - example: 'true', - default_field: false, - }, - ], - }, - { - name: 'container', - title: 'Container', - group: 2, - description: - 'Container fields are used for meta information about the specific\ncontainer that is the source of information.\n\nThese fields help correlate data based containers from any runtime.', - type: 'group', - fields: [ - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique container id.', - }, - { - name: 'image.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the image the container was built on.', - }, - { - name: 'image.tag', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Container image tags.', - }, - { - name: 'labels', - level: 'extended', - type: 'object', - object_type: 'keyword', - description: 'Image labels.', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Container name.', - }, - { - name: 'runtime', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Runtime managing this container.', - example: 'docker', - }, - ], - }, - { - name: 'destination', - title: 'Destination', - group: 2, - description: - 'Destination fields describe details about the destination of a packet/event.\n\nDestination fields are usually populated in conjunction with source fields.', - type: 'group', - fields: [ - { - name: 'address', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Some event destination addresses are defined ambiguously. The\nevent will sometimes list an IP, a domain or a unix socket. You should always\nstore the raw address in the `.address` field.\n\nThen it should be duplicated to `.ip` or `.domain`, depending on which one\nit is.', - }, - { - name: 'as.number', - level: 'extended', - type: 'long', - description: - 'Unique number allocated to the autonomous system. The autonomous\nsystem number (ASN) uniquely identifies each network on the Internet.', - example: 15169, - }, - { - name: 'as.organization.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Organization name.', - example: 'Google LLC', - }, - { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - description: 'Bytes sent from the destination to the source.', - example: 184, - }, - { - name: 'domain', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Destination domain.', - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: - 'IP address of the destination.\n\nCan be one or multiple IPv4 or IPv6 addresses.', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'MAC address of the destination.', - }, - { - name: 'nat.ip', - level: 'extended', - type: 'ip', - description: - 'Translated ip of destination based NAT sessions (e.g. internet\nto private DMZ)\n\nTypically used with load balancers, firewalls, or routers.', - }, - { - name: 'nat.port', - level: 'extended', - type: 'long', - format: 'string', - description: - 'Port the source session is translated to by NAT Device.\n\nTypically used with load balancers, firewalls, or routers.', - }, - { - name: 'packets', - level: 'core', - type: 'long', - description: 'Packets sent from the destination to the source.', - example: 12, - }, - { - name: 'port', - level: 'core', - type: 'long', - format: 'string', - description: 'Port of the destination.', - }, - { - name: 'registered_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The highest registered destination domain, stripped of the subdomain.\n\nFor example, the registered domain for "foo.google.com" is "google.com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last two labels will not work well for TLDs such as "co.uk".', - example: 'google.com', - }, - { - name: 'top_level_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The effective top level domain (eTLD), also known as the domain\nsuffix, is the last part of the domain name. For example, the top level domain\nfor google.com is "com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last label will not work well for effective TLDs such as "co.uk".', - example: 'co.uk', - }, - { - name: 'user.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the user is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.email', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'User email address.', - }, - { - name: 'user.full_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'user.group.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.group.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'user.group.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - { - name: 'user.hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'user.id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifiers of the user.', - }, - { - name: 'user.name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'dll', - title: 'DLL', - group: 2, - description: - 'These fields contain information about code libraries dynamically\nloaded into processes.\n\n\nMany operating systems refer to "shared code libraries" with different names,\nbut this field set refers to all of the following:\n\n* Dynamic-link library (`.dll`) commonly used on Windows\n\n* Shared Object (`.so`) commonly used on Unix-like operating systems\n\n* Dynamic library (`.dylib`) commonly used on macOS', - type: 'group', - fields: [ - { - name: 'code_signature.exists', - level: 'core', - type: 'boolean', - description: 'Boolean to capture if a signature is present.', - example: 'true', - default_field: false, - }, - { - name: 'code_signature.status', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Additional information about the certificate status.\n\nThis is useful for logging cryptographic errors with the certificate validity\nor trust status. Leave unpopulated if the validity or trust of the certificate\nwas unchecked.', - example: 'ERROR_UNTRUSTED_ROOT', - default_field: false, - }, - { - name: 'code_signature.subject_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Subject name of the code signer', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'code_signature.trusted', - level: 'extended', - type: 'boolean', - description: - 'Stores the trust status of the certificate chain.\n\nValidating the trust of the certificate chain may be complicated, and this\nfield should only be populated by tools that actively check the status.', - example: 'true', - default_field: false, - }, - { - name: 'code_signature.valid', - level: 'extended', - type: 'boolean', - description: - 'Boolean to capture if the digital signature is verified against\nthe binary content.\n\nLeave unpopulated if a certificate was unchecked.', - example: 'true', - default_field: false, - }, - { - name: 'hash.md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'MD5 hash.', - default_field: false, - }, - { - name: 'hash.sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA1 hash.', - default_field: false, - }, - { - name: 'hash.sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA256 hash.', - default_field: false, - }, - { - name: 'hash.sha512', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA512 hash.', - default_field: false, - }, - { - name: 'name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the library.\n\nThis generally maps to the name of the file on disk.', - example: 'kernel32.dll', - default_field: false, - }, - { - name: 'path', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Full file path of the library.', - example: 'C:\\Windows\\System32\\kernel32.dll', - default_field: false, - }, - { - name: 'pe.company', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal company name of the file, provided at compile-time.', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'pe.description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal description of the file, provided at compile-time.', - example: 'Paint', - default_field: false, - }, - { - name: 'pe.file_version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal version of the file, provided at compile-time.', - example: '6.3.9600.17415', - default_field: false, - }, - { - name: 'pe.original_file_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal name of the file, provided at compile-time.', - example: 'MSPAINT.EXE', - default_field: false, - }, - { - name: 'pe.product', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal product name of the file, provided at compile-time.', - example: 'Microsoft® Windows® Operating System', - default_field: false, - }, - ], - }, - { - name: 'dns', - title: 'DNS', - group: 2, - description: - 'Fields describing DNS queries and answers.\n\nDNS events should either represent a single DNS query prior to getting answers\n(`dns.type:query`) or they should represent a full exchange and contain the\nquery details as well as all of the answers that were provided for this query\n(`dns.type:answer`).', - type: 'group', - fields: [ - { - name: 'answers', - level: 'extended', - type: 'object', - object_type: 'keyword', - description: - 'An array containing an object for each answer section returned\nby the server.\n\nThe main keys that should be present in these objects are defined by ECS.\nRecords that have more information may contain more keys than what ECS defines.\n\nNot all DNS data sources give all details about DNS answers. At minimum, answer\nobjects must contain the `data` key. If more information is available, map\nas much of it to ECS as possible, and add any additional fields to the answer\nobjects as custom fields.', - }, - { - name: 'answers.class', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The class of DNS data contained in this resource record.', - example: 'IN', - }, - { - name: 'answers.data', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The data describing the resource.\n\nThe meaning of this data depends on the type and class of the resource record.', - example: '10.10.10.10', - }, - { - name: 'answers.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The domain name to which this resource record pertains.\n\nIf a chain of CNAME is being resolved, each answer `name` should be the\none that corresponds with the answer `data`. It should not simply be the\noriginal `question.name` repeated.', - example: 'www.google.com', - }, - { - name: 'answers.ttl', - level: 'extended', - type: 'long', - description: - 'The time interval in seconds that this resource record may be cached\nbefore it should be discarded. Zero values mean that the data should not be\ncached.', - example: 180, - }, - { - name: 'answers.type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The type of data contained in this resource record.', - example: 'CNAME', - }, - { - name: 'header_flags', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Array of 2 letter DNS header flags.\n\nExpected values are: AA, TC, RD, RA, AD, CD, DO.', - example: ['RD', 'RA'], - }, - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The DNS packet identifier assigned by the program that generated\nthe query. The identifier is copied to the response.', - example: 62111, - }, - { - name: 'op_code', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The DNS operation code that specifies the kind of query in the\nmessage. This value is set by the originator of a query and copied into the\nresponse.', - example: 'QUERY', - }, - { - name: 'question.class', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The class of records being queried.', - example: 'IN', - }, - { - name: 'question.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The name being queried.\n\nIf the name field contains non-printable characters (below 32 or above 126),\nthose characters should be represented as escaped base 10 integers (\\DDD).\nBack slashes and quotes should be escaped. Tabs, carriage returns, and line\nfeeds should be converted to \\t, \\r, and \\n respectively.', - example: 'www.google.com', - }, - { - name: 'question.registered_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The highest registered domain, stripped of the subdomain.\n\nFor example, the registered domain for "foo.google.com" is "google.com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last two labels will not work well for TLDs such as "co.uk".', - example: 'google.com', - }, - { - name: 'question.subdomain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The subdomain is all of the labels under the registered_domain.\n\nIf the domain has multiple levels of subdomain, such as "sub2.sub1.example.com",\nthe subdomain field should contain "sub2.sub1", with no trailing period.', - example: 'www', - }, - { - name: 'question.top_level_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The effective top level domain (eTLD), also known as the domain\nsuffix, is the last part of the domain name. For example, the top level domain\nfor google.com is "com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last label will not work well for effective TLDs such as "co.uk".', - example: 'co.uk', - }, - { - name: 'question.type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The type of record being queried.', - example: 'AAAA', - }, - { - name: 'resolved_ip', - level: 'extended', - type: 'ip', - description: - 'Array containing all IPs seen in `answers.data`.\n\nThe `answers` array can be difficult to use, because of the variety of data\nformats it can contain. Extracting all IP addresses seen in there to `dns.resolved_ip`\nmakes it possible to index them as IP addresses, and makes them easier to\nvisualize and query for.', - example: ['10.10.10.10', '10.10.10.11'], - }, - { - name: 'response_code', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The DNS response code.', - example: 'NOERROR', - }, - { - name: 'type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The type of DNS event captured, query or answer.\n\nIf your source of DNS events only gives you DNS queries, you should only create\ndns events of type `dns.type:query`.\n\nIf your source of DNS events gives you answers as well, you should create\none event per query (optionally as soon as the query is seen). And a second\nevent containing all query details as well as an array of answers.', - example: 'answer', - }, - ], - }, - { - name: 'ecs', - title: 'ECS', - group: 2, - description: 'Meta-information specific to ECS.', - type: 'group', - fields: [ - { - name: 'version', - level: 'core', - required: true, - type: 'keyword', - ignore_above: 1024, - description: - 'ECS version this event conforms to. `ecs.version` is a required\nfield and must exist in all events.\n\nWhen querying across multiple indices -- which may conform to slightly different\nECS versions -- this field lets integrations adjust to the schema version\nof the events.', - example: '1.0.0', - }, - ], - }, - { - name: 'error', - title: 'Error', - group: 2, - description: - 'These fields can represent errors of any kind.\n\nUse them for errors that happen while fetching events or in cases where the\nevent itself contains an error.', - type: 'group', - fields: [ - { - name: 'code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Error code describing the error.', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the error.', - }, - { - name: 'message', - level: 'core', - type: 'text', - description: 'Error message.', - }, - { - name: 'stack_trace', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'The stack trace of this error in plain text.', - }, - { - name: 'type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The type of the error, for example the class name of the exception.', - example: 'java.lang.NullPointerException', - }, - ], - }, - { - name: 'event', - title: 'Event', - group: 2, - description: - 'The event fields are used for context information about the log\nor metric event itself.\n\nA log is defined as an event containing details of something that happened.\nLog events must include the time at which the thing happened. Examples of log\nevents include a process starting on a host, a network packet being sent from\na source to a destination, or a network connection between a client and a server\nbeing initiated or closed. A metric is defined as an event containing one or\nmore numerical measurements and the time at which the measurement was taken.\nExamples of metric events include memory pressure measured on a host and device\ntemperature. See the `event.kind` definition in this section for additional\ndetails about metric and state events.', - type: 'group', - fields: [ - { - name: 'action', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'The action captured by the event.\n\nThis describes the information in the event. It is more specific than `event.category`.\nExamples are `group-add`, `process-started`, `file-created`. The value is\nnormally defined by the implementer.', - example: 'user-password-change', - }, - { - name: 'category', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'This is one of four ECS Categorization Fields, and indicates the\nsecond level in the ECS category hierarchy.\n\n`event.category` represents the "big buckets" of ECS categories. For example,\nfiltering on `event.category:process` yields all events relating to process\nactivity. This field is closely related to `event.type`, which is used as\na subcategory.\n\nThis field is an array. This will allow proper categorization of some events\nthat fall in multiple categories.', - example: 'authentication', - }, - { - name: 'code', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Identification code for this event, if one exists.\n\nSome event sources use event codes to identify messages unambiguously, regardless\nof message language or wording adjustments over time. An example of this is\nthe Windows Event ID.', - example: 4648, - }, - { - name: 'created', - level: 'core', - type: 'date', - description: - 'event.created contains the date/time when the event was first\nread by an agent, or by your pipeline.\n\nThis field is distinct from @timestamp in that @timestamp typically contain\nthe time extracted from the original event.\n\nIn most situations, these two timestamps will be slightly different. The difference\ncan be used to calculate the delay between your source generating an event,\nand the time when your agent first processed it. This can be used to monitor\nyour agent or pipeline ability to keep up with your event source.\n\nIn case the two timestamps are identical, @timestamp should be used.', - example: '2016-05-23T08:05:34.857Z', - }, - { - name: 'dataset', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the dataset.\n\nIf an event source publishes more than one type of log or events (e.g. access\nlog, error log), the dataset is used to specify which one the event comes\nfrom.\n\nIt is recommended but not required to start the dataset name with the module\nname, followed by a dot, then the dataset name.', - example: 'apache.access', - }, - { - name: 'duration', - level: 'core', - type: 'long', - format: 'duration', - input_format: 'nanoseconds', - output_format: 'asMilliseconds', - output_precision: 1, - description: - 'Duration of the event in nanoseconds.\n\nIf event.start and event.end are known this value should be the difference\nbetween the end and start time.', - }, - { - name: 'end', - level: 'extended', - type: 'date', - description: - 'event.end contains the date when the event ended or when the activity\nwas last observed.', - }, - { - name: 'hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Hash (perhaps logstash fingerprint) of raw field to be able to\ndemonstrate log integrity.', - example: '123456789012345678901234567890ABCD', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique ID to describe the event.', - example: '8a4f500d', - }, - { - name: 'ingested', - level: 'core', - type: 'date', - description: - 'Timestamp when an event arrived in the central data store.\n\nThis is different from `@timestamp`, which is when the event originally occurred. It is\nalso different from `event.created`, which is meant to capture the first time\nan agent saw the event.\n\nIn normal conditions, assuming no tampering, the timestamps should chronologically\nlook like this: `@timestamp` < `event.created` < `event.ingested`.', - example: '2016-05-23T08:05:35.101Z', - default_field: false, - }, - { - name: 'kind', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'This is one of four ECS Categorization Fields, and indicates the\nhighest level in the ECS category hierarchy.\n\n`event.kind` gives high-level information about what type of information the\nevent contains, without being specific to the contents of the event. For example,\nvalues of this field distinguish alert events from metric events.\n\nThe value of this field can be used to inform how these kinds of events should\nbe handled. They may warrant different retention, different access control,\nit may also help understand whether the data coming in at a regular interval\nor not.', - example: 'alert', - }, - { - name: 'module', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the module this data is coming from.\n\nIf your monitoring agent supports the concept of modules or plugins to process\nevents of a given source (e.g. Apache logs), `event.module` should contain\nthe name of this module.', - example: 'apache', - }, - { - name: 'original', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Raw text message of entire event. Used to demonstrate log integrity.\n\nThis field is not indexed and doc_values are disabled. It cannot be searched,\nbut it can be retrieved from `_source`.', - example: - 'Sep 19 08:26:10 host CEF:0|Security| threatmanager|1.0|100|\nworm successfully stopped|10|src=10.0.0.1 dst=2.1.2.2spt=1232', - }, - { - name: 'outcome', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'This is one of four ECS Categorization Fields, and indicates the\nlowest level in the ECS category hierarchy.\n\n`event.outcome` simply denotes whether the event represents a success or a\nfailure from the perspective of the entity that produced the event.\n\nNote that when a single transaction is described in multiple events, each\nevent may populate different values of `event.outcome`, according to their\nperspective.\n\nAlso note that in the case of a compound event (a single event that contains\nmultiple logical events), this field should be populated with the value that\nbest captures the overall success or failure from the perspective of the event\nproducer.\n\nFurther note that not all events will have an associated outcome. For example,\nthis field is generally not populated for metric events, events with `event.type:info`,\nor any events for which an outcome does not make logical sense.', - example: 'success', - }, - { - name: 'provider', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Source of the event.\n\nEvent transports such as Syslog or the Windows Event Log typically mention\nthe source of an event. It can be the name of the software that generated\nthe event (e.g. Sysmon, httpd), or of a subsystem of the operating system\n(kernel, Microsoft-Windows-Security-Auditing).', - example: 'kernel', - }, - { - name: 'reference', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Reference URL linking to additional information about this event.\n\nThis URL links to a static definition of the this event. Alert events, indicated\nby `event.kind:alert`, are a common use case for this field.', - example: 'https://system.vendor.com/event/#0001234', - default_field: false, - }, - { - name: 'risk_score', - level: 'core', - type: 'float', - description: - "Risk score or priority of the event (e.g. security solutions).\nUse your system's original value here.", - }, - { - name: 'risk_score_norm', - level: 'extended', - type: 'float', - description: - 'Normalized risk score or priority of the event, on a scale of\n0 to 100.\n\nThis is mainly useful if you use more than one system that assigns risk scores,\nand you want to see a normalized value across all systems.', - }, - { - name: 'sequence', - level: 'extended', - type: 'long', - format: 'string', - description: - 'Sequence number of the event.\n\nThe sequence number is a value published by some event sources, to make the\nexact ordering of events unambiguous, regardless of the timestamp precision.', - }, - { - name: 'severity', - level: 'core', - type: 'long', - format: 'string', - description: - 'The numeric severity of the event according to your event source.\n\nWhat the different severity values mean can be different between sources and\nuse cases. It is up to the implementer to make sure severities are consistent\nacross events from the same source.\n\nThe Syslog severity belongs in `log.syslog.severity.code`. `event.severity`\nis meant to represent the severity according to the event source (e.g. firewall,\nIDS). If the event source does not publish its own severity, you may optionally\ncopy the `log.syslog.severity.code` to `event.severity`.', - example: 7, - }, - { - name: 'start', - level: 'extended', - type: 'date', - description: - 'event.start contains the date when the event started or when the\nactivity was first observed.', - }, - { - name: 'timezone', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'This field should be populated when the event timestamp does\nnot include timezone information already (e.g. default Syslog timestamps).\nIt is optional otherwise.\n\nAcceptable timezone formats are: a canonical ID (e.g. "Europe/Amsterdam"),\nabbreviated (e.g. "EST") or an HH:mm differential (e.g. "-05:00").', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'This is one of four ECS Categorization Fields, and indicates the\nthird level in the ECS category hierarchy.\n\n`event.type` represents a categorization "sub-bucket" that, when used along\nwith the `event.category` field values, enables filtering events down to a\nlevel appropriate for single visualization.\n\nThis field is an array. This will allow proper categorization of some events\nthat fall in multiple event types.', - }, - { - name: 'url', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'URL linking to an external system to continue investigation of\nthis event.\n\nThis URL links to another system where in-depth investigation of the specific\noccurence of this event can take place. Alert events, indicated by `event.kind:alert`,\nare a common use case for this field.', - example: 'https://mysystem.mydomain.com/alert/5271dedb-f5b0-4218-87f0-4ac4870a38fe', - default_field: false, - }, - ], - }, - { - name: 'file', - title: 'File', - group: 2, - description: - 'A file is defined as a set of information that has been created\non, or has existed on a filesystem.\n\nFile objects can be associated with host events, network events, and/or file\nevents (e.g., those produced by File Integrity Monitoring [FIM] products or\nservices). File fields provide details about the affected file associated with\nthe event or metric.', - type: 'group', - fields: [ - { - name: 'accessed', - level: 'extended', - type: 'date', - description: - 'Last time the file was accessed.\n\nNote that not all filesystems keep track of access time.', - }, - { - name: 'attributes', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Array of file attributes.\n\nAttributes names will vary by platform. Here is a non-exhaustive list of values\nthat are expected in this field: archive, compressed, directory, encrypted,\nexecute, hidden, read, readonly, system, write.', - example: '["readonly", "system"]', - default_field: false, - }, - { - name: 'code_signature.exists', - level: 'core', - type: 'boolean', - description: 'Boolean to capture if a signature is present.', - example: 'true', - default_field: false, - }, - { - name: 'code_signature.status', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Additional information about the certificate status.\n\nThis is useful for logging cryptographic errors with the certificate validity\nor trust status. Leave unpopulated if the validity or trust of the certificate\nwas unchecked.', - example: 'ERROR_UNTRUSTED_ROOT', - default_field: false, - }, - { - name: 'code_signature.subject_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Subject name of the code signer', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'code_signature.trusted', - level: 'extended', - type: 'boolean', - description: - 'Stores the trust status of the certificate chain.\n\nValidating the trust of the certificate chain may be complicated, and this\nfield should only be populated by tools that actively check the status.', - example: 'true', - default_field: false, - }, - { - name: 'code_signature.valid', - level: 'extended', - type: 'boolean', - description: - 'Boolean to capture if the digital signature is verified against\nthe binary content.\n\nLeave unpopulated if a certificate was unchecked.', - example: 'true', - default_field: false, - }, - { - name: 'created', - level: 'extended', - type: 'date', - description: - 'File creation time.\n\nNote that not all filesystems store the creation time.', - }, - { - name: 'ctime', - level: 'extended', - type: 'date', - description: - 'Last time the file attributes or metadata changed.\n\nNote that changes to the file content will update `mtime`. This implies `ctime`\nwill be adjusted at the same time, since `mtime` is an attribute of the file.', - }, - { - name: 'device', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Device that is the source of the file.', - example: 'sda', - }, - { - name: 'directory', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Directory where the file is located. It should include the drive\nletter, when appropriate.', - example: '/home/alice', - }, - { - name: 'drive_letter', - level: 'extended', - type: 'keyword', - ignore_above: 1, - description: - 'Drive letter where the file is located. This field is only relevant\non Windows.\n\nThe value should be uppercase, and not include the colon.', - example: 'C', - default_field: false, - }, - { - name: 'extension', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'File extension.', - example: 'png', - }, - { - name: 'gid', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Primary group ID (GID) of the file.', - example: '1001', - }, - { - name: 'group', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Primary group name of the file.', - example: 'alice', - }, - { - name: 'hash.md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'MD5 hash.', - }, - { - name: 'hash.sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA1 hash.', - }, - { - name: 'hash.sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA256 hash.', - }, - { - name: 'hash.sha512', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA512 hash.', - }, - { - name: 'inode', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Inode representing the file in the filesystem.', - example: '256383', - }, - { - name: 'mime_type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'MIME type should identify the format of the file or stream of bytes\nusing https://www.iana.org/assignments/media-types/media-types.xhtml[IANA\nofficial types], where possible. When more than one type is applicable, the\nmost specific type should be used.', - default_field: false, - }, - { - name: 'mode', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Mode of the file in octal representation.', - example: '0640', - }, - { - name: 'mtime', - level: 'extended', - type: 'date', - description: 'Last time the file content was modified.', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the file including the extension, without the directory.', - example: 'example.png', - }, - { - name: 'owner', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: "File owner's username.", - example: 'alice', - }, - { - name: 'path', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: - 'Full path to the file, including the file name. It should include\nthe drive letter, when appropriate.', - example: '/home/alice/example.png', - }, - { - name: 'pe.company', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal company name of the file, provided at compile-time.', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'pe.description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal description of the file, provided at compile-time.', - example: 'Paint', - default_field: false, - }, - { - name: 'pe.file_version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal version of the file, provided at compile-time.', - example: '6.3.9600.17415', - default_field: false, - }, - { - name: 'pe.original_file_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal name of the file, provided at compile-time.', - example: 'MSPAINT.EXE', - default_field: false, - }, - { - name: 'pe.product', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal product name of the file, provided at compile-time.', - example: 'Microsoft® Windows® Operating System', - default_field: false, - }, - { - name: 'size', - level: 'extended', - type: 'long', - description: 'File size in bytes.\n\nOnly relevant when `file.type` is "file".', - example: 16384, - }, - { - name: 'target_path', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Target path for symlinks.', - }, - { - name: 'type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'File type (file, dir, or symlink).', - example: 'file', - }, - { - name: 'uid', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The user ID (UID) or security identifier (SID) of the file owner.', - example: '1001', - }, - ], - }, - { - name: 'geo', - title: 'Geo', - group: 2, - description: - 'Geo fields can carry data about a specific location related to an\nevent.\n\nThis geolocation information can be derived from techniques such as Geo IP,\nor be user-supplied.', - type: 'group', - fields: [ - { - name: 'city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - ], - }, - { - name: 'group', - title: 'Group', - group: 2, - description: - 'The group fields are meant to represent groups that are relevant\nto the event.', - type: 'group', - fields: [ - { - name: 'domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - ], - }, - { - name: 'hash', - title: 'Hash', - group: 2, - description: - 'The hash fields represent different hash algorithms and their values.\n\nField names for common hashes (e.g. MD5, SHA1) are predefined. Add fields for\nother hashes by lowercasing the hash algorithm name and using underscore separators\nas appropriate (snake case, e.g. sha3_512).', - type: 'group', - fields: [ - { - name: 'md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'MD5 hash.', - }, - { - name: 'sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA1 hash.', - }, - { - name: 'sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA256 hash.', - }, - { - name: 'sha512', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA512 hash.', - }, - ], - }, - { - name: 'host', - title: 'Host', - group: 2, - description: - 'A host is defined as a general computing instance.\n\nECS host.* fields should be populated with details about the host on which the\nevent happened, or from which the measurement was taken. Host types include\nhardware, virtual machines, Docker containers, and Kubernetes nodes.', - type: 'group', - fields: [ - { - name: 'architecture', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system architecture.', - example: 'x86_64', - }, - { - name: 'domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the domain of which the host is a member.\n\nFor example, on Windows this could be the host Active Directory domain\nor NetBIOS domain name. For Linux this could be the domain of the host\nLDAP provider.', - example: 'CONTOSO', - default_field: false, - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'hostname', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Hostname of the host.\n\nIt normally contains what the `hostname` command returns on the host machine.', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique host id.\n\nAs hostname is not always unique, use values that are meaningful in your environment.\n\nExample: The current usage of `beat.name`.', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: 'Host ip addresses.', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Host mac addresses.', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the host.\n\nIt can contain what `hostname` returns on Unix systems, the fully qualified\ndomain name, or a name specified by the user. The sender decides which value\nto use.', - }, - { - name: 'os.family', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'OS family (such as redhat, debian, freebsd, windows).', - example: 'debian', - }, - { - name: 'os.full', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, including the version or code name.', - example: 'Mac OS Mojave', - }, - { - name: 'os.kernel', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system kernel version as a raw string.', - example: '4.4.0-112-generic', - }, - { - name: 'os.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, without the version.', - example: 'Mac OS X', - }, - { - name: 'os.platform', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'os.version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system version as a raw string.', - example: '10.14.1', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Type of host.\n\nFor Cloud providers this can be the machine type like `t2.medium`. If vm,\nthis could be the container, for example, or other information meaningful\nin your environment.', - }, - { - name: 'uptime', - level: 'extended', - type: 'long', - description: 'Seconds the host has been up.', - example: 1325, - }, - { - name: 'user.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the user is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.email', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'User email address.', - }, - { - name: 'user.full_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'user.group.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.group.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'user.group.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - { - name: 'user.hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'user.id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifiers of the user.', - }, - { - name: 'user.name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'http', - title: 'HTTP', - group: 2, - description: - 'Fields related to HTTP activity. Use the `url` field set to store\nthe url of the request.', - type: 'group', - fields: [ - { - name: 'request.body.bytes', - level: 'extended', - type: 'long', - format: 'bytes', - description: 'Size in bytes of the request body.', - example: 887, - }, - { - name: 'request.body.content', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'The full HTTP request body.', - example: 'Hello world', - }, - { - name: 'request.bytes', - level: 'extended', - type: 'long', - format: 'bytes', - description: 'Total size in bytes of the request (body and headers).', - example: 1437, - }, - { - name: 'request.method', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'HTTP request method.\n\nThe field value must be normalized to lowercase for querying. See the documentation\nsection "Implementing ECS".', - example: 'get, post, put', - }, - { - name: 'request.referrer', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Referrer for this HTTP request.', - example: 'https://blog.example.com/', - }, - { - name: 'response.body.bytes', - level: 'extended', - type: 'long', - format: 'bytes', - description: 'Size in bytes of the response body.', - example: 887, - }, - { - name: 'response.body.content', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'The full HTTP response body.', - example: 'Hello world', - }, - { - name: 'response.bytes', - level: 'extended', - type: 'long', - format: 'bytes', - description: 'Total size in bytes of the response (body and headers).', - example: 1437, - }, - { - name: 'response.status_code', - level: 'extended', - type: 'long', - format: 'string', - description: 'HTTP response status code.', - example: 404, - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'HTTP version.', - example: 1.1, - }, - ], - }, - { - name: 'interface', - title: 'Interface', - group: 2, - description: - 'The interface fields are used to record ingress and egress interface\ninformation when reported by an observer (e.g. firewall, router, load balancer)\nin the context of the observer handling a network connection. In the case of\na single observer interface (e.g. network sensor on a span port) only the observer.ingress\ninformation should be populated.', - type: 'group', - fields: [ - { - name: 'alias', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Interface alias as reported by the system, typically used in firewall\nimplementations for e.g. inside, outside, or dmz logical interface naming.', - example: 'outside', - default_field: false, - }, - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Interface ID as reported by an observer (typically SNMP interface\nID).', - example: 10, - default_field: false, - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Interface name as reported by the system.', - example: 'eth0', - default_field: false, - }, - ], - }, - { - name: 'log', - title: 'Log', - group: 2, - description: - 'Details about the event logging mechanism or logging transport.\n\nThe log.* fields are typically populated with details about the logging mechanism\nused to create and/or transport the event. For example, syslog details belong\nunder `log.syslog.*`.\n\nThe details specific to your event source are typically not logged under `log.*`,\nbut rather in `event.*` or in other ECS fields.', - type: 'group', - fields: [ - { - name: 'level', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Original log level of the log event.\n\nIf the source of the event provides a log level or textual severity, this\nis the one that goes in `log.level`. If your source does not specify one,\nyou may put your event transport severity here (e.g. Syslog severity).\n\nSome examples are `warn`, `err`, `i`, `informational`.', - example: 'error', - }, - { - name: 'logger', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'The name of the logger inside an application. This is usually the\nname of the class which initialized the logger, or can be a custom name.', - example: 'org.elasticsearch.bootstrap.Bootstrap', - }, - { - name: 'origin.file.line', - level: 'extended', - type: 'integer', - description: - 'The line number of the file containing the source code which originated\nthe log event.', - example: 42, - }, - { - name: 'origin.file.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The name of the file containing the source code which originated\nthe log event. Note that this is not the name of the log file.', - example: 'Bootstrap.java', - }, - { - name: 'origin.function', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The name of the function or method which originated the log event.', - example: 'init', - }, - { - name: 'original', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'This is the original log message and contains the full log message\nbefore splitting it up in multiple parts.\n\nIn contrast to the `message` field which can contain an extracted part of\nthe log message, this field contains the original, full log message. It can\nhave already some modifications applied like encoding or new lines removed\nto clean up the log message.\n\nThis field is not indexed and doc_values are disabled so it cannot be queried\nbut the value can be retrieved from `_source`.', - example: 'Sep 19 08:26:10 localhost My log', - }, - { - name: 'syslog', - level: 'extended', - type: 'object', - object_type: 'keyword', - description: - 'The Syslog metadata of the event, if the event was transmitted\nvia Syslog. Please see RFCs 5424 or 3164.', - }, - { - name: 'syslog.facility.code', - level: 'extended', - type: 'long', - format: 'string', - description: - 'The Syslog numeric facility of the log event, if available.\n\nAccording to RFCs 5424 and 3164, this value should be an integer between 0\nand 23.', - example: 23, - }, - { - name: 'syslog.facility.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The Syslog text-based facility of the log event, if available.', - example: 'local7', - }, - { - name: 'syslog.priority', - level: 'extended', - type: 'long', - format: 'string', - description: - 'Syslog numeric priority of the event, if available.\n\nAccording to RFCs 5424 and 3164, the priority is 8 * facility + severity.\nThis number is therefore expected to contain a value between 0 and 191.', - example: 135, - }, - { - name: 'syslog.severity.code', - level: 'extended', - type: 'long', - description: - 'The Syslog numeric severity of the log event, if available.\n\nIf the event source publishing via Syslog provides a different numeric severity\nvalue (e.g. firewall, IDS), your source numeric severity should go to `event.severity`.\nIf the event source does not specify a distinct severity, you can optionally\ncopy the Syslog severity to `event.severity`.', - example: 3, - }, - { - name: 'syslog.severity.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The Syslog numeric severity of the log event, if available.\n\nIf the event source publishing via Syslog provides a different severity value\n(e.g. firewall, IDS), your source text severity should go to `log.level`.\nIf the event source does not specify a distinct severity, you can optionally\ncopy the Syslog severity to `log.level`.', - example: 'Error', - }, - ], - }, - { - name: 'network', - title: 'Network', - group: 2, - description: - 'The network is defined as the communication path over which a host\nor network event happens.\n\nThe network.* fields should be populated with details about the network activity\nassociated with an event.', - type: 'group', - fields: [ - { - name: 'application', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A name given to an application level protocol. This can be arbitrarily\nassigned for things like microservices, but also apply to things like skype,\nicq, facebook, twitter. This would be used in situations where the vendor\nor service can be decoded such as from the source/dest IP owners, ports, or\nwire format.\n\nThe field value must be normalized to lowercase for querying. See the documentation\nsection "Implementing ECS".', - example: 'aim', - }, - { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - description: - 'Total bytes transferred in both directions.\n\nIf `source.bytes` and `destination.bytes` are known, `network.bytes` is their\nsum.', - example: 368, - }, - { - name: 'community_id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A hash of source and destination IPs and ports, as well as the\nprotocol used in a communication. This is a tool-agnostic standard to identify\nflows.\n\nLearn more at https://github.com/corelight/community-id-spec.', - example: '1:hO+sN4H+MG5MY/8hIrXPqc4ZQz0=', - }, - { - name: 'direction', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - "Direction of the network traffic.\nRecommended values are:\n * inbound\n * outbound\n * internal\n * external\n * unknown\n\nWhen mapping events from a host-based monitoring context, populate this field from the host's point of view.\nWhen mapping events from a network or perimeter-based monitoring context, populate this field from the point of view of your network perimeter.", - example: 'inbound', - }, - { - name: 'forwarded_ip', - level: 'core', - type: 'ip', - description: 'Host IP address when the source IP address is the proxy.', - example: '192.1.1.2', - }, - { - name: 'iana_number', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'IANA Protocol Number (https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml).\nStandardized list of protocols. This aligns well with NetFlow and sFlow related\nlogs which use the IANA Protocol Number.', - example: 6, - }, - { - name: 'inner', - level: 'extended', - type: 'object', - object_type: 'keyword', - description: - 'Network.inner fields are added in addition to network.vlan fields\nto describe the innermost VLAN when q-in-q VLAN tagging is present. Allowed\nfields include vlan.id and vlan.name. Inner vlan fields are typically used\nwhen sending traffic with multiple 802.1q encapsulations to a network sensor\n(e.g. Zeek, Wireshark.)', - default_field: false, - }, - { - name: 'inner.vlan.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'VLAN ID as reported by the observer.', - example: 10, - default_field: false, - }, - { - name: 'inner.vlan.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Optional VLAN name as reported by the observer.', - example: 'outside', - default_field: false, - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name given by operators to sections of their network.', - example: 'Guest Wifi', - }, - { - name: 'packets', - level: 'core', - type: 'long', - description: - 'Total packets transferred in both directions.\n\nIf `source.packets` and `destination.packets` are known, `network.packets`\nis their sum.', - example: 24, - }, - { - name: 'protocol', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'L7 Network protocol name. ex. http, lumberjack, transport protocol.\n\nThe field value must be normalized to lowercase for querying. See the documentation\nsection "Implementing ECS".', - example: 'http', - }, - { - name: 'transport', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Same as network.iana_number, but instead using the Keyword name\nof the transport layer (udp, tcp, ipv6-icmp, etc.)\n\nThe field value must be normalized to lowercase for querying. See the documentation\nsection "Implementing ECS".', - example: 'tcp', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'In the OSI Model this would be the Network Layer. ipv4, ipv6,\nipsec, pim, etc\n\nThe field value must be normalized to lowercase for querying. See the documentation\nsection "Implementing ECS".', - example: 'ipv4', - }, - { - name: 'vlan.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'VLAN ID as reported by the observer.', - example: 10, - default_field: false, - }, - { - name: 'vlan.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Optional VLAN name as reported by the observer.', - example: 'outside', - default_field: false, - }, - ], - }, - { - name: 'observer', - title: 'Observer', - group: 2, - description: - 'An observer is defined as a special network, security, or application\ndevice used to detect, observe, or create network, security, or application-related\nevents and metrics.\n\nThis could be a custom hardware appliance or a server that has been configured\nto run special network, security, or application software. Examples include\nfirewalls, web proxies, intrusion detection/prevention systems, network monitoring\nsensors, web application firewalls, data loss prevention systems, and APM servers.\nThe observer.* fields shall be populated with details of the system, if any,\nthat detects, observes and/or creates a network, security, or application event\nor metric. Message queues and ETL components used in processing events or metrics\nare not considered observers in ECS.', - type: 'group', - fields: [ - { - name: 'egress', - level: 'extended', - type: 'object', - object_type: 'keyword', - description: - 'Observer.egress holds information like interface number and name,\nvlan, and zone information to classify egress traffic. Single armed monitoring\nsuch as a network sensor on a span port should only use observer.ingress\nto categorize traffic.', - default_field: false, - }, - { - name: 'egress.interface.alias', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Interface alias as reported by the system, typically used in firewall\nimplementations for e.g. inside, outside, or dmz logical interface naming.', - example: 'outside', - default_field: false, - }, - { - name: 'egress.interface.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Interface ID as reported by an observer (typically SNMP interface\nID).', - example: 10, - default_field: false, - }, - { - name: 'egress.interface.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Interface name as reported by the system.', - example: 'eth0', - default_field: false, - }, - { - name: 'egress.vlan.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'VLAN ID as reported by the observer.', - example: 10, - default_field: false, - }, - { - name: 'egress.vlan.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Optional VLAN name as reported by the observer.', - example: 'outside', - default_field: false, - }, - { - name: 'egress.zone', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Network zone of outbound traffic as reported by the observer to\ncategorize the destination area of egress traffic, e.g. Internal, External,\nDMZ, HR, Legal, etc.', - example: 'Public_Internet', - default_field: false, - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'hostname', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Hostname of the observer.', - }, - { - name: 'ingress', - level: 'extended', - type: 'object', - object_type: 'keyword', - description: - 'Observer.ingress holds information like interface number and name,\nvlan, and zone information to classify ingress traffic. Single armed monitoring\nsuch as a network sensor on a span port should only use observer.ingress\nto categorize traffic.', - default_field: false, - }, - { - name: 'ingress.interface.alias', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Interface alias as reported by the system, typically used in firewall\nimplementations for e.g. inside, outside, or dmz logical interface naming.', - example: 'outside', - default_field: false, - }, - { - name: 'ingress.interface.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Interface ID as reported by an observer (typically SNMP interface\nID).', - example: 10, - default_field: false, - }, - { - name: 'ingress.interface.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Interface name as reported by the system.', - example: 'eth0', - default_field: false, - }, - { - name: 'ingress.vlan.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'VLAN ID as reported by the observer.', - example: 10, - default_field: false, - }, - { - name: 'ingress.vlan.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Optional VLAN name as reported by the observer.', - example: 'outside', - default_field: false, - }, - { - name: 'ingress.zone', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Network zone of incoming traffic as reported by the observer to\ncategorize the source area of ingress traffic. e.g. internal, External, DMZ,\nHR, Legal, etc.', - example: 'DMZ', - default_field: false, - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: 'IP addresses of the observer.', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'MAC addresses of the observer', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Custom name of the observer.\n\nThis is a name that can be given to an observer. This can be helpful for example\nif multiple firewalls of the same model are used in an organization.\n\nIf no custom name is needed, the field can be left empty.', - example: '1_proxySG', - }, - { - name: 'os.family', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'OS family (such as redhat, debian, freebsd, windows).', - example: 'debian', - }, - { - name: 'os.full', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, including the version or code name.', - example: 'Mac OS Mojave', - }, - { - name: 'os.kernel', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system kernel version as a raw string.', - example: '4.4.0-112-generic', - }, - { - name: 'os.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, without the version.', - example: 'Mac OS X', - }, - { - name: 'os.platform', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'os.version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system version as a raw string.', - example: '10.14.1', - }, - { - name: 'product', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The product name of the observer.', - example: 's200', - }, - { - name: 'serial_number', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Observer serial number.', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'The type of the observer the data is coming from.\n\nThere is no predefined list of observer types. Some examples are `forwarder`,\n`firewall`, `ids`, `ips`, `proxy`, `poller`, `sensor`, `APM server`.', - example: 'firewall', - }, - { - name: 'vendor', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Vendor name of the observer.', - example: 'Symantec', - }, - { - name: 'version', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Observer version.', - }, - ], - }, - { - name: 'organization', - title: 'Organization', - group: 2, - description: - 'The organization fields enrich data with information about the company\nor entity the data is associated with.\n\nThese fields help you arrange or filter data stored in an index by one or multiple\norganizations.', - type: 'group', - fields: [ - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the organization.', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Organization name.', - }, - ], - }, - { - name: 'os', - title: 'Operating System', - group: 2, - description: 'The OS fields contain information about the operating system.', - type: 'group', - fields: [ - { - name: 'family', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'OS family (such as redhat, debian, freebsd, windows).', - example: 'debian', - }, - { - name: 'full', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, including the version or code name.', - example: 'Mac OS Mojave', - }, - { - name: 'kernel', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system kernel version as a raw string.', - example: '4.4.0-112-generic', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, without the version.', - example: 'Mac OS X', - }, - { - name: 'platform', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system version as a raw string.', - example: '10.14.1', - }, - ], - }, - { - name: 'package', - title: 'Package', - group: 2, - description: - 'These fields contain information about an installed software package.\nIt contains general information about a package, such as name, version or size.\nIt also contains installation details, such as time or location.', - type: 'group', - fields: [ - { - name: 'architecture', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Package architecture.', - example: 'x86_64', - }, - { - name: 'build_version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Additional information about the build version of the installed\npackage.\n\nFor example use the commit SHA of a non-released package.', - example: '36f4f7e89dd61b0988b12ee000b98966867710cd', - default_field: false, - }, - { - name: 'checksum', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Checksum of the installed package for verification.', - example: '68b329da9893e34099c7d8ad5cb9c940', - }, - { - name: 'description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Description of the package.', - example: - 'Open source programming language to build simple/reliable/efficient\nsoftware.', - }, - { - name: 'install_scope', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Indicating how the package was installed, e.g. user-local, global.', - example: 'global', - }, - { - name: 'installed', - level: 'extended', - type: 'date', - description: 'Time when package was installed.', - }, - { - name: 'license', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'License under which the package was released.\n\nUse a short name, e.g. the license identifier from SPDX License List where\npossible (https://spdx.org/licenses/).', - example: 'Apache License 2.0', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Package name', - example: 'go', - }, - { - name: 'path', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Path where the package is installed.', - example: '/usr/local/Cellar/go/1.12.9/', - }, - { - name: 'reference', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Home page or reference URL of the software in this package, if\navailable.', - example: 'https://golang.org', - default_field: false, - }, - { - name: 'size', - level: 'extended', - type: 'long', - format: 'string', - description: 'Package size in bytes.', - example: 62231, - }, - { - name: 'type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Type of package.\n\nThis should contain the package file type, rather than the package manager\nname. Examples: rpm, dpkg, brew, npm, gem, nupkg, jar.', - example: 'rpm', - default_field: false, - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Package version', - example: '1.12.9', - }, - ], - }, - { - name: 'pe', - title: 'PE Header', - group: 2, - description: 'These fields contain Windows Portable Executable (PE) metadata.', - type: 'group', - fields: [ - { - name: 'company', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal company name of the file, provided at compile-time.', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal description of the file, provided at compile-time.', - example: 'Paint', - default_field: false, - }, - { - name: 'file_version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal version of the file, provided at compile-time.', - example: '6.3.9600.17415', - default_field: false, - }, - { - name: 'original_file_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal name of the file, provided at compile-time.', - example: 'MSPAINT.EXE', - default_field: false, - }, - { - name: 'product', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal product name of the file, provided at compile-time.', - example: 'Microsoft® Windows® Operating System', - default_field: false, - }, - ], - }, - { - name: 'process', - title: 'Process', - group: 2, - description: - 'These fields contain information about a process.\n\nThese fields can help you correlate metrics information with a process id/name\nfrom a log message. The `process.pid` often stays in the metric itself and\nis copied to the global field for correlation.', - type: 'group', - fields: [ - { - name: 'args', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.', - example: ['/usr/bin/ssh', '-l', 'user', '10.0.0.16'], - }, - { - name: 'args_count', - level: 'extended', - type: 'long', - description: - 'Length of the process.args array.\n\nThis field can be useful for querying or performing bucket analysis on how\nmany arguments were provided to start a process. More arguments may be an\nindication of suspicious activity.', - example: 4, - default_field: false, - }, - { - name: 'code_signature.exists', - level: 'core', - type: 'boolean', - description: 'Boolean to capture if a signature is present.', - example: 'true', - default_field: false, - }, - { - name: 'code_signature.status', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Additional information about the certificate status.\n\nThis is useful for logging cryptographic errors with the certificate validity\nor trust status. Leave unpopulated if the validity or trust of the certificate\nwas unchecked.', - example: 'ERROR_UNTRUSTED_ROOT', - default_field: false, - }, - { - name: 'code_signature.subject_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Subject name of the code signer', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'code_signature.trusted', - level: 'extended', - type: 'boolean', - description: - 'Stores the trust status of the certificate chain.\n\nValidating the trust of the certificate chain may be complicated, and this\nfield should only be populated by tools that actively check the status.', - example: 'true', - default_field: false, - }, - { - name: 'code_signature.valid', - level: 'extended', - type: 'boolean', - description: - 'Boolean to capture if the digital signature is verified against\nthe binary content.\n\nLeave unpopulated if a certificate was unchecked.', - example: 'true', - default_field: false, - }, - { - name: 'command_line', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: - 'Full command line that started the process, including the absolute\npath to the executable, and all arguments.\n\nSome arguments may be filtered to protect sensitive information.', - example: '/usr/bin/ssh -l user 10.0.0.16', - default_field: false, - }, - { - name: 'entity_id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique identifier for the process.\n\nThe implementation of this is specified by the data source, but some examples\nof what could be used here are a process-generated UUID, Sysmon Process GUIDs,\nor a hash of some uniquely identifying components of a process.\n\nConstructing a globally unique identifier is a common practice to mitigate\nPID reuse as well as to identify a specific process over time, across multiple\nmonitored hosts.', - example: 'c2c455d9f99375d', - default_field: false, - }, - { - name: 'executable', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Absolute path to the process executable.', - example: '/usr/bin/ssh', - }, - { - name: 'exit_code', - level: 'extended', - type: 'long', - description: - 'The exit code of the process, if this is a termination event.\n\nThe field should be absent if there is no exit code for the event (e.g. process\nstart).', - example: 137, - default_field: false, - }, - { - name: 'hash.md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'MD5 hash.', - }, - { - name: 'hash.sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA1 hash.', - }, - { - name: 'hash.sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA256 hash.', - }, - { - name: 'hash.sha512', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA512 hash.', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Process name.\n\nSometimes called program name or similar.', - example: 'ssh', - }, - { - name: 'parent.args', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Array of process arguments.\n\nMay be filtered to protect sensitive information.', - example: ['ssh', '-l', 'user', '10.0.0.16'], - default_field: false, - }, - { - name: 'parent.args_count', - level: 'extended', - type: 'long', - description: - 'Length of the process.args array.\n\nThis field can be useful for querying or performing bucket analysis on how\nmany arguments were provided to start a process. More arguments may be an\nindication of suspicious activity.', - example: 4, - default_field: false, - }, - { - name: 'parent.code_signature.exists', - level: 'core', - type: 'boolean', - description: 'Boolean to capture if a signature is present.', - example: 'true', - default_field: false, - }, - { - name: 'parent.code_signature.status', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Additional information about the certificate status.\n\nThis is useful for logging cryptographic errors with the certificate validity\nor trust status. Leave unpopulated if the validity or trust of the certificate\nwas unchecked.', - example: 'ERROR_UNTRUSTED_ROOT', - default_field: false, - }, - { - name: 'parent.code_signature.subject_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Subject name of the code signer', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'parent.code_signature.trusted', - level: 'extended', - type: 'boolean', - description: - 'Stores the trust status of the certificate chain.\n\nValidating the trust of the certificate chain may be complicated, and this\nfield should only be populated by tools that actively check the status.', - example: 'true', - default_field: false, - }, - { - name: 'parent.code_signature.valid', - level: 'extended', - type: 'boolean', - description: - 'Boolean to capture if the digital signature is verified against\nthe binary content.\n\nLeave unpopulated if a certificate was unchecked.', - example: 'true', - default_field: false, - }, - { - name: 'parent.command_line', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: - 'Full command line that started the process, including the absolute\npath to the executable, and all arguments.\n\nSome arguments may be filtered to protect sensitive information.', - example: '/usr/bin/ssh -l user 10.0.0.16', - default_field: false, - }, - { - name: 'parent.entity_id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique identifier for the process.\n\nThe implementation of this is specified by the data source, but some examples\nof what could be used here are a process-generated UUID, Sysmon Process GUIDs,\nor a hash of some uniquely identifying components of a process.\n\nConstructing a globally unique identifier is a common practice to mitigate\nPID reuse as well as to identify a specific process over time, across multiple\nmonitored hosts.', - example: 'c2c455d9f99375d', - default_field: false, - }, - { - name: 'parent.executable', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: 'Absolute path to the process executable.', - example: '/usr/bin/ssh', - default_field: false, - }, - { - name: 'parent.exit_code', - level: 'extended', - type: 'long', - description: - 'The exit code of the process, if this is a termination event.\n\nThe field should be absent if there is no exit code for the event (e.g. process\nstart).', - example: 137, - default_field: false, - }, - { - name: 'parent.hash.md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'MD5 hash.', - default_field: false, - }, - { - name: 'parent.hash.sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA1 hash.', - default_field: false, - }, - { - name: 'parent.hash.sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA256 hash.', - default_field: false, - }, - { - name: 'parent.hash.sha512', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA512 hash.', - default_field: false, - }, - { - name: 'parent.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: 'Process name.\n\nSometimes called program name or similar.', - example: 'ssh', - default_field: false, - }, - { - name: 'parent.pgid', - level: 'extended', - type: 'long', - format: 'string', - description: 'Identifier of the group of processes the process belongs to.', - default_field: false, - }, - { - name: 'parent.pid', - level: 'core', - type: 'long', - format: 'string', - description: 'Process id.', - example: 4242, - default_field: false, - }, - { - name: 'parent.ppid', - level: 'extended', - type: 'long', - format: 'string', - description: "Parent process' pid.", - example: 4241, - default_field: false, - }, - { - name: 'parent.start', - level: 'extended', - type: 'date', - description: 'The time the process started.', - example: '2016-05-23T08:05:34.853Z', - default_field: false, - }, - { - name: 'parent.thread.id', - level: 'extended', - type: 'long', - format: 'string', - description: 'Thread ID.', - example: 4242, - default_field: false, - }, - { - name: 'parent.thread.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Thread name.', - example: 'thread-0', - default_field: false, - }, - { - name: 'parent.title', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: - 'Process title.\n\nThe proctitle, some times the same as process name. Can also be different:\nfor example a browser setting its title to the web page currently opened.', - default_field: false, - }, - { - name: 'parent.uptime', - level: 'extended', - type: 'long', - description: 'Seconds the process has been up.', - example: 1325, - default_field: false, - }, - { - name: 'parent.working_directory', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: 'The working directory of the process.', - example: '/home/alice', - default_field: false, - }, - { - name: 'pe.company', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal company name of the file, provided at compile-time.', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'pe.description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal description of the file, provided at compile-time.', - example: 'Paint', - default_field: false, - }, - { - name: 'pe.file_version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal version of the file, provided at compile-time.', - example: '6.3.9600.17415', - default_field: false, - }, - { - name: 'pe.original_file_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal name of the file, provided at compile-time.', - example: 'MSPAINT.EXE', - default_field: false, - }, - { - name: 'pe.product', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal product name of the file, provided at compile-time.', - example: 'Microsoft® Windows® Operating System', - default_field: false, - }, - { - name: 'pgid', - level: 'extended', - type: 'long', - format: 'string', - description: 'Identifier of the group of processes the process belongs to.', - }, - { - name: 'pid', - level: 'core', - type: 'long', - format: 'string', - description: 'Process id.', - example: 4242, - }, - { - name: 'ppid', - level: 'extended', - type: 'long', - format: 'string', - description: "Parent process' pid.", - example: 4241, - }, - { - name: 'start', - level: 'extended', - type: 'date', - description: 'The time the process started.', - example: '2016-05-23T08:05:34.853Z', - }, - { - name: 'thread.id', - level: 'extended', - type: 'long', - format: 'string', - description: 'Thread ID.', - example: 4242, - }, - { - name: 'thread.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Thread name.', - example: 'thread-0', - }, - { - name: 'title', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: - 'Process title.\n\nThe proctitle, some times the same as process name. Can also be different:\nfor example a browser setting its title to the web page currently opened.', - }, - { - name: 'uptime', - level: 'extended', - type: 'long', - description: 'Seconds the process has been up.', - example: 1325, - }, - { - name: 'working_directory', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'The working directory of the process.', - example: '/home/alice', - }, - ], - }, - { - name: 'registry', - title: 'Registry', - group: 2, - description: 'Fields related to Windows Registry operations.', - type: 'group', - fields: [ - { - name: 'data.bytes', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Original bytes written with base64 encoding.\n\nFor Windows registry operations, such as SetValueEx and RegQueryValueEx, this\ncorresponds to the data pointed by `lp_data`. This is optional but provides\nbetter recoverability and should be populated for REG_BINARY encoded values.', - example: 'ZQBuAC0AVQBTAAAAZQBuAAAAAAA=', - default_field: false, - }, - { - name: 'data.strings', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Content when writing string types.\n\nPopulated as an array when writing string data to the registry. For single\nstring registry types (REG_SZ, REG_EXPAND_SZ), this should be an array with\none string. For sequences of string with REG_MULTI_SZ, this array will be\nvariable length. For numeric data, such as REG_DWORD and REG_QWORD, this should\nbe populated with the decimal representation (e.g `"1"`).', - example: '["C:\\rta\\red_ttp\\bin\\myapp.exe"]', - default_field: false, - }, - { - name: 'data.type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Standard registry type for encoding contents', - example: 'REG_SZ', - default_field: false, - }, - { - name: 'hive', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Abbreviated name for the hive.', - example: 'HKLM', - default_field: false, - }, - { - name: 'key', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Hive-relative path of keys.', - example: - 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\winword.exe', - default_field: false, - }, - { - name: 'path', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Full path, including hive, key and value', - example: - 'HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution\nOptions\\winword.exe\\Debugger', - default_field: false, - }, - { - name: 'value', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the value written.', - example: 'Debugger', - default_field: false, - }, - ], - }, - { - name: 'related', - title: 'Related', - group: 2, - description: - 'This field set is meant to facilitate pivoting around a piece of\ndata.\n\nSome pieces of information can be seen in many places in an ECS event. To facilitate\nsearching for them, store an array of all seen values to their corresponding\nfield in `related.`.\n\nA concrete example is IP addresses, which can be under host, observer, source,\ndestination, client, server, and network.forwarded_ip. If you append all IPs\nto `related.ip`, you can then search for a given IP trivially, no matter where\nit appeared, by querying `related.ip:192.0.2.15`.', - type: 'group', - fields: [ - { - name: 'hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - "All the hashes seen on your event. Populating this field, then\nusing it to search for hashes can help in situations where you're unsure what\nthe hash algorithm is (and therefore which key name to search).", - default_field: false, - }, - { - name: 'ip', - level: 'extended', - type: 'ip', - description: 'All of the IPs seen on your event.', - }, - { - name: 'user', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'All the user names seen on your event.', - default_field: false, - }, - ], - }, - { - name: 'rule', - title: 'Rule', - group: 2, - description: - 'Rule fields are used to capture the specifics of any observer or\nagent rules that generate alerts or other notable events.\n\nExamples of data sources that would populate the rule fields include: network\nadmission control platforms, network or host IDS/IPS, network firewalls, web\napplication firewalls, url filters, endpoint detection and response (EDR) systems,\netc.', - type: 'group', - fields: [ - { - name: 'author', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name, organization, or pseudonym of the author or authors who created\nthe rule used to generate this event.', - example: ['Star-Lord'], - default_field: false, - }, - { - name: 'category', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A categorization value keyword used by the entity using the rule\nfor detection of this event.', - example: 'Attempted Information Leak', - default_field: false, - }, - { - name: 'description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The description of the rule generating the event.', - example: 'Block requests to public DNS over HTTPS / TLS protocols', - default_field: false, - }, - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A rule ID that is unique within the scope of an agent, observer,\nor other entity using the rule for detection of this event.', - example: 101, - default_field: false, - }, - { - name: 'license', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the license under which the rule used to generate this\nevent is made available.', - example: 'Apache 2.0', - default_field: false, - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The name of the rule or signature generating the event.', - example: 'BLOCK_DNS_over_TLS', - default_field: false, - }, - { - name: 'reference', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Reference URL to additional information about the rule used to\ngenerate this event.\n\nThe URL can point to the vendor documentation about the rule. If that is\nnot available, it can also be a link to a more general page describing this\ntype of alert.', - example: 'https://en.wikipedia.org/wiki/DNS_over_TLS', - default_field: false, - }, - { - name: 'ruleset', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the ruleset, policy, group, or parent category in which\nthe rule used to generate this event is a member.', - example: 'Standard_Protocol_Filters', - default_field: false, - }, - { - name: 'uuid', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A rule ID that is unique within the scope of a set or group of\nagents, observers, or other entities using the rule for detection of this\nevent.', - example: 1100110011, - default_field: false, - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The version / revision of the rule being used for analysis.', - example: 1.1, - default_field: false, - }, - ], - }, - { - name: 'server', - title: 'Server', - group: 2, - description: - 'A Server is defined as the responder in a network connection for\nevents regarding sessions, connections, or bidirectional flow records.\n\nFor TCP events, the server is the receiver of the initial SYN packet(s) of the\nTCP connection. For other protocols, the server is generally the responder in\nthe network transaction. Some systems actually use the term "responder" to refer\nthe server in TCP connections. The server fields describe details about the\nsystem acting as the server in the network event. Server fields are usually\npopulated in conjunction with client fields. Server fields are generally not\npopulated for packet-level events.\n\nClient / server representations can add semantic context to an exchange, which\nis helpful to visualize the data in certain situations. If your context falls\nin that category, you should still ensure that source and destination are filled\nappropriately.', - type: 'group', - fields: [ - { - name: 'address', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Some event server addresses are defined ambiguously. The event\nwill sometimes list an IP, a domain or a unix socket. You should always store\nthe raw address in the `.address` field.\n\nThen it should be duplicated to `.ip` or `.domain`, depending on which one\nit is.', - }, - { - name: 'as.number', - level: 'extended', - type: 'long', - description: - 'Unique number allocated to the autonomous system. The autonomous\nsystem number (ASN) uniquely identifies each network on the Internet.', - example: 15169, - }, - { - name: 'as.organization.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Organization name.', - example: 'Google LLC', - }, - { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - description: 'Bytes sent from the server to the client.', - example: 184, - }, - { - name: 'domain', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Server domain.', - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: - 'IP address of the server.\n\nCan be one or multiple IPv4 or IPv6 addresses.', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'MAC address of the server.', - }, - { - name: 'nat.ip', - level: 'extended', - type: 'ip', - description: - 'Translated ip of destination based NAT sessions (e.g. internet\nto private DMZ)\n\nTypically used with load balancers, firewalls, or routers.', - }, - { - name: 'nat.port', - level: 'extended', - type: 'long', - format: 'string', - description: - 'Translated port of destination based NAT sessions (e.g. internet\nto private DMZ)\n\nTypically used with load balancers, firewalls, or routers.', - }, - { - name: 'packets', - level: 'core', - type: 'long', - description: 'Packets sent from the server to the client.', - example: 12, - }, - { - name: 'port', - level: 'core', - type: 'long', - format: 'string', - description: 'Port of the server.', - }, - { - name: 'registered_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The highest registered server domain, stripped of the subdomain.\n\nFor example, the registered domain for "foo.google.com" is "google.com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last two labels will not work well for TLDs such as "co.uk".', - example: 'google.com', - }, - { - name: 'top_level_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The effective top level domain (eTLD), also known as the domain\nsuffix, is the last part of the domain name. For example, the top level domain\nfor google.com is "com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last label will not work well for effective TLDs such as "co.uk".', - example: 'co.uk', - }, - { - name: 'user.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the user is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.email', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'User email address.', - }, - { - name: 'user.full_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'user.group.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.group.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'user.group.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - { - name: 'user.hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'user.id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifiers of the user.', - }, - { - name: 'user.name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'service', - title: 'Service', - group: 2, - description: - 'The service fields describe the service for or from which the data\nwas collected.\n\nThese fields help you find and correlate logs for a specific service and version.', - type: 'group', - fields: [ - { - name: 'ephemeral_id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Ephemeral identifier of this service (if one exists).\n\nThis id normally changes across restarts, but `service.id` does not.', - example: '8a4f500f', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique identifier of the running service. If the service is comprised\nof many nodes, the `service.id` should be the same for all nodes.\n\nThis id should uniquely identify the service. This makes it possible to correlate\nlogs and metrics for one specific service, no matter which particular node\nemitted the event.\n\nNote that if you need to see the events from one specific host of the service,\nyou should filter on that `host.name` or `host.id` instead.', - example: 'd37e5ebfe0ae6c4972dbe9f0174a1637bb8247f6', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the service data is collected from.\n\nThe name of the service is normally user given. This allows for distributed\nservices that run on multiple hosts to correlate the related instances based\non the name.\n\nIn the case of Elasticsearch the `service.name` could contain the cluster\nname. For Beats the `service.name` is by default a copy of the `service.type`\nfield if no name is specified.', - example: 'elasticsearch-metrics', - }, - { - name: 'node.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of a service node.\n\nThis allows for two nodes of the same service running on the same host to\nbe differentiated. Therefore, `service.node.name` should typically be unique\nacross nodes of a given service.\n\nIn the case of Elasticsearch, the `service.node.name` could contain the unique\nnode name within the Elasticsearch cluster. In cases where the service does not\nhave the concept of a node name, the host name or container name can be used\nto distinguish running instances that make up this service. If those do not\nprovide uniqueness (e.g. multiple instances of the service running on the\nsame host) - the node name can be manually set.', - example: 'instance-0000000016', - }, - { - name: 'state', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Current state of the service.', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'The type of the service data is collected from.\n\nThe type can be used to group and correlate logs and metrics from one service\ntype.\n\nExample: If logs or metrics are collected from Elasticsearch, `service.type`\nwould be `elasticsearch`.', - example: 'elasticsearch', - }, - { - name: 'version', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Version of the service the data was collected from.\n\nThis allows to look at a data set only for a specific version of a service.', - example: '3.2.4', - }, - ], - }, - { - name: 'source', - title: 'Source', - group: 2, - description: - 'Source fields describe details about the source of a packet/event.\n\nSource fields are usually populated in conjunction with destination fields.', - type: 'group', - fields: [ - { - name: 'address', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Some event source addresses are defined ambiguously. The event\nwill sometimes list an IP, a domain or a unix socket. You should always store\nthe raw address in the `.address` field.\n\nThen it should be duplicated to `.ip` or `.domain`, depending on which one\nit is.', - }, - { - name: 'as.number', - level: 'extended', - type: 'long', - description: - 'Unique number allocated to the autonomous system. The autonomous\nsystem number (ASN) uniquely identifies each network on the Internet.', - example: 15169, - }, - { - name: 'as.organization.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Organization name.', - example: 'Google LLC', - }, - { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - description: 'Bytes sent from the source to the destination.', - example: 184, - }, - { - name: 'domain', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Source domain.', - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: - 'IP address of the source.\n\nCan be one or multiple IPv4 or IPv6 addresses.', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'MAC address of the source.', - }, - { - name: 'nat.ip', - level: 'extended', - type: 'ip', - description: - 'Translated ip of source based NAT sessions (e.g. internal client\nto internet)\n\nTypically connections traversing load balancers, firewalls, or routers.', - }, - { - name: 'nat.port', - level: 'extended', - type: 'long', - format: 'string', - description: - 'Translated port of source based NAT sessions. (e.g. internal client\nto internet)\n\nTypically used with load balancers, firewalls, or routers.', - }, - { - name: 'packets', - level: 'core', - type: 'long', - description: 'Packets sent from the source to the destination.', - example: 12, - }, - { - name: 'port', - level: 'core', - type: 'long', - format: 'string', - description: 'Port of the source.', - }, - { - name: 'registered_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The highest registered source domain, stripped of the subdomain.\n\nFor example, the registered domain for "foo.google.com" is "google.com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last two labels will not work well for TLDs such as "co.uk".', - example: 'google.com', - }, - { - name: 'top_level_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The effective top level domain (eTLD), also known as the domain\nsuffix, is the last part of the domain name. For example, the top level domain\nfor google.com is "com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last label will not work well for effective TLDs such as "co.uk".', - example: 'co.uk', - }, - { - name: 'user.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the user is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.email', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'User email address.', - }, - { - name: 'user.full_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'user.group.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.group.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'user.group.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - { - name: 'user.hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'user.id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifiers of the user.', - }, - { - name: 'user.name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'threat', - title: 'Threat', - group: 2, - description: - 'Fields to classify events and alerts according to a threat taxonomy\nsuch as the Mitre ATT&CK framework.\n\nThese fields are for users to classify alerts from all of their sources (e.g.\nIDS, NGFW, etc.) within a common taxonomy. The threat.tactic.* are meant to\ncapture the high level category of the threat (e.g. "impact"). The threat.technique.*\nfields are meant to capture which kind of approach is used by this detected\nthreat, to accomplish the goal (e.g. "endpoint denial of service").', - type: 'group', - fields: [ - { - name: 'framework', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the threat framework used to further categorize and classify\nthe tactic and technique of the reported threat. Framework classification\ncan be provided by detecting systems, evaluated at ingest time, or retrospectively\ntagged to events.', - example: 'MITRE ATT&CK', - }, - { - name: 'tactic.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The id of tactic used by this threat. You can use the Mitre ATT&CK\nMatrix Tactic categorization, for example. (ex. https://attack.mitre.org/tactics/TA0040/\n)', - example: 'TA0040', - }, - { - name: 'tactic.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the type of tactic used by this threat. You can use the\nMitre ATT&CK Matrix Tactic categorization, for example. (ex. https://attack.mitre.org/tactics/TA0040/\n)', - example: 'impact', - }, - { - name: 'tactic.reference', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The reference url of tactic used by this threat. You can use the\nMitre ATT&CK Matrix Tactic categorization, for example. (ex. https://attack.mitre.org/tactics/TA0040/\n)', - example: 'https://attack.mitre.org/tactics/TA0040/', - }, - { - name: 'technique.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The id of technique used by this tactic. You can use the Mitre\nATT&CK Matrix Tactic categorization, for example. (ex. https://attack.mitre.org/techniques/T1499/\n)', - example: 'T1499', - }, - { - name: 'technique.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: - 'The name of technique used by this tactic. You can use the Mitre\nATT&CK Matrix Tactic categorization, for example. (ex. https://attack.mitre.org/techniques/T1499/\n)', - example: 'endpoint denial of service', - }, - { - name: 'technique.reference', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The reference url of technique used by this tactic. You can use\nthe Mitre ATT&CK Matrix Tactic categorization, for example. (ex. https://attack.mitre.org/techniques/T1499/\n)', - example: 'https://attack.mitre.org/techniques/T1499/', - }, - ], - }, - { - name: 'tls', - title: 'TLS', - group: 2, - description: - 'Fields related to a TLS connection. These fields focus on the TLS\nprotocol itself and intentionally avoids in-depth analysis of the related x.509\ncertificate files.', - type: 'group', - fields: [ - { - name: 'cipher', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'String indicating the cipher used during the current connection.', - example: 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256', - default_field: false, - }, - { - name: 'client.certificate', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'PEM-encoded stand-alone certificate offered by the client. This\nis usually mutually-exclusive of `client.certificate_chain` since this value\nalso exists in that list.', - example: 'MII...', - default_field: false, - }, - { - name: 'client.certificate_chain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Array of PEM-encoded certificates that make up the certificate\nchain offered by the client. This is usually mutually-exclusive of `client.certificate`\nsince that value should be the first certificate in the chain.', - example: ['MII...', 'MII...'], - default_field: false, - }, - { - name: 'client.hash.md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Certificate fingerprint using the MD5 digest of DER-encoded version\nof certificate offered by the client. For consistency with other hash values,\nthis value should be formatted as an uppercase hash.', - example: '0F76C7F2C55BFD7D8E8B8F4BFBF0C9EC', - default_field: false, - }, - { - name: 'client.hash.sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Certificate fingerprint using the SHA1 digest of DER-encoded version\nof certificate offered by the client. For consistency with other hash values,\nthis value should be formatted as an uppercase hash.', - example: '9E393D93138888D288266C2D915214D1D1CCEB2A', - default_field: false, - }, - { - name: 'client.hash.sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Certificate fingerprint using the SHA256 digest of DER-encoded\nversion of certificate offered by the client. For consistency with other hash\nvalues, this value should be formatted as an uppercase hash.', - example: '0687F666A054EF17A08E2F2162EAB4CBC0D265E1D7875BE74BF3C712CA92DAF0', - default_field: false, - }, - { - name: 'client.issuer', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Distinguished name of subject of the issuer of the x.509 certificate\npresented by the client.', - example: 'CN=MyDomain Root CA, OU=Infrastructure Team, DC=mydomain, DC=com', - default_field: false, - }, - { - name: 'client.ja3', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A hash that identifies clients based on how they perform an SSL/TLS\nhandshake.', - example: 'd4e5b18d6b55c71272893221c96ba240', - default_field: false, - }, - { - name: 'client.not_after', - level: 'extended', - type: 'date', - description: - 'Date/Time indicating when client certificate is no longer considered\nvalid.', - example: '2021-01-01T00:00:00.000Z', - default_field: false, - }, - { - name: 'client.not_before', - level: 'extended', - type: 'date', - description: 'Date/Time indicating when client certificate is first considered\nvalid.', - example: '1970-01-01T00:00:00.000Z', - default_field: false, - }, - { - name: 'client.server_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Also called an SNI, this tells the server which hostname to which\nthe client is attempting to connect. When this value is available, it should\nget copied to `destination.domain`.', - example: 'www.elastic.co', - default_field: false, - }, - { - name: 'client.subject', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Distinguished name of subject of the x.509 certificate presented\nby the client.', - example: 'CN=myclient, OU=Documentation Team, DC=mydomain, DC=com', - default_field: false, - }, - { - name: 'client.supported_ciphers', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Array of ciphers offered by the client during the client hello.', - example: [ - 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384', - 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384', - '...', - ], - default_field: false, - }, - { - name: 'curve', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'String indicating the curve used for the given cipher, when applicable.', - example: 'secp256r1', - default_field: false, - }, - { - name: 'established', - level: 'extended', - type: 'boolean', - description: - 'Boolean flag indicating if the TLS negotiation was successful and\ntransitioned to an encrypted tunnel.', - default_field: false, - }, - { - name: 'next_protocol', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'String indicating the protocol being tunneled. Per the values in\nthe IANA registry (https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids),\nthis string should be lower case.', - example: 'http/1.1', - default_field: false, - }, - { - name: 'resumed', - level: 'extended', - type: 'boolean', - description: - 'Boolean flag indicating if this TLS connection was resumed from\nan existing TLS negotiation.', - default_field: false, - }, - { - name: 'server.certificate', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'PEM-encoded stand-alone certificate offered by the server. This\nis usually mutually-exclusive of `server.certificate_chain` since this value\nalso exists in that list.', - example: 'MII...', - default_field: false, - }, - { - name: 'server.certificate_chain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Array of PEM-encoded certificates that make up the certificate\nchain offered by the server. This is usually mutually-exclusive of `server.certificate`\nsince that value should be the first certificate in the chain.', - example: ['MII...', 'MII...'], - default_field: false, - }, - { - name: 'server.hash.md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Certificate fingerprint using the MD5 digest of DER-encoded version\nof certificate offered by the server. For consistency with other hash values,\nthis value should be formatted as an uppercase hash.', - example: '0F76C7F2C55BFD7D8E8B8F4BFBF0C9EC', - default_field: false, - }, - { - name: 'server.hash.sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Certificate fingerprint using the SHA1 digest of DER-encoded version\nof certificate offered by the server. For consistency with other hash values,\nthis value should be formatted as an uppercase hash.', - example: '9E393D93138888D288266C2D915214D1D1CCEB2A', - default_field: false, - }, - { - name: 'server.hash.sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Certificate fingerprint using the SHA256 digest of DER-encoded\nversion of certificate offered by the server. For consistency with other hash\nvalues, this value should be formatted as an uppercase hash.', - example: '0687F666A054EF17A08E2F2162EAB4CBC0D265E1D7875BE74BF3C712CA92DAF0', - default_field: false, - }, - { - name: 'server.issuer', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Subject of the issuer of the x.509 certificate presented by the\nserver.', - example: 'CN=MyDomain Root CA, OU=Infrastructure Team, DC=mydomain, DC=com', - default_field: false, - }, - { - name: 'server.ja3s', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A hash that identifies servers based on how they perform an SSL/TLS\nhandshake.', - example: '394441ab65754e2207b1e1b457b3641d', - default_field: false, - }, - { - name: 'server.not_after', - level: 'extended', - type: 'date', - description: - 'Timestamp indicating when server certificate is no longer considered\nvalid.', - example: '2021-01-01T00:00:00.000Z', - default_field: false, - }, - { - name: 'server.not_before', - level: 'extended', - type: 'date', - description: 'Timestamp indicating when server certificate is first considered\nvalid.', - example: '1970-01-01T00:00:00.000Z', - default_field: false, - }, - { - name: 'server.subject', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Subject of the x.509 certificate presented by the server.', - example: 'CN=www.mydomain.com, OU=Infrastructure Team, DC=mydomain, DC=com', - default_field: false, - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Numeric part of the version parsed from the original string.', - example: '1.2', - default_field: false, - }, - { - name: 'version_protocol', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Normalized lowercase protocol name parsed from original string.', - example: 'tls', - default_field: false, - }, - ], - }, - { - name: 'tracing', - title: 'Tracing', - group: 2, - description: - 'Distributed tracing makes it possible to analyze performance throughout\na microservice architecture all in one view. This is accomplished by tracing\nall of the requests - from the initial web request in the front-end service\n- to queries made through multiple back-end services.', - type: 'group', - fields: [ - { - name: 'trace.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique identifier of the trace.\n\nA trace groups multiple events like transactions that belong together. For\nexample, a user request handled by multiple inter-connected services.', - example: '4bf92f3577b34da6a3ce929d0e0e4736', - }, - { - name: 'transaction.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique identifier of the transaction.\n\nA transaction is the highest level of work measured within a service, such\nas a request to a server.', - example: '00f067aa0ba902b7', - }, - ], - }, - { - name: 'url', - title: 'URL', - group: 2, - description: - 'URL fields provide support for complete or partial URLs, and supports\nthe breaking down into scheme, domain, path, and so on.', - type: 'group', - fields: [ - { - name: 'domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Domain of the url, such as "www.elastic.co".\n\nIn some cases a URL may refer to an IP and/or port directly, without a domain\nname. In this case, the IP address would go to the `domain` field.', - example: 'www.elastic.co', - }, - { - name: 'extension', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The field contains the file extension from the original request\nurl.\n\nThe file extension is only set if it exists, as not every url has a file extension.\n\nThe leading period must not be included. For example, the value must be "png",\nnot ".png".', - example: 'png', - }, - { - name: 'fragment', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Portion of the url after the `#`, such as "top".\n\nThe `#` is not part of the fragment.', - }, - { - name: 'full', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: - 'If full URLs are important to your use case, they should be stored\nin `url.full`, whether this field is reconstructed or present in the event\nsource.', - example: 'https://www.elastic.co:443/search?q=elasticsearch#top', - }, - { - name: 'original', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: - 'Unmodified original url as seen in the event source.\n\nNote that in network monitoring, the observed URL may be a full URL, whereas\nin access logs, the URL is often just represented as a path.\n\nThis field is meant to represent the URL as it was observed, complete or not.', - example: - 'https://www.elastic.co:443/search?q=elasticsearch#top or /search?q=elasticsearch', - }, - { - name: 'password', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Password of the request.', - }, - { - name: 'path', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Path of the request, such as "/search".', - }, - { - name: 'port', - level: 'extended', - type: 'long', - format: 'string', - description: 'Port of the request, such as 443.', - example: 443, - }, - { - name: 'query', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The query field describes the query string of the request, such\nas "q=elasticsearch".\n\nThe `?` is excluded from the query string. If a URL contains no `?`, there\nis no query field. If there is a `?` but no query, the query field exists\nwith an empty string. The `exists` query can be used to differentiate between\nthe two cases.', - }, - { - name: 'registered_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The highest registered url domain, stripped of the subdomain.\n\nFor example, the registered domain for "foo.google.com" is "google.com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last two labels will not work well for TLDs such as "co.uk".', - example: 'google.com', - }, - { - name: 'scheme', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Scheme of the request, such as "https".\n\nNote: The `:` is not part of the scheme.', - example: 'https', - }, - { - name: 'top_level_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The effective top level domain (eTLD), also known as the domain\nsuffix, is the last part of the domain name. For example, the top level domain\nfor google.com is "com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last label will not work well for effective TLDs such as "co.uk".', - example: 'co.uk', - }, - { - name: 'username', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Username of the request.', - }, - ], - }, - { - name: 'user', - title: 'User', - group: 2, - description: - 'The user fields describe information about the user that is relevant\nto the event.\n\nFields can have one entry or multiple entries. If a user has more than one id,\nprovide an array that includes all of them.', - type: 'group', - fields: [ - { - name: 'domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the user is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'email', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'User email address.', - }, - { - name: 'full_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'group.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'group.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'group.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - { - name: 'hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifiers of the user.', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'user_agent', - title: 'User agent', - group: 2, - description: - 'The user_agent fields normally come from a browser request.\n\nThey often show up in web service logs coming from the parsed user agent string.', - type: 'group', - fields: [ - { - name: 'device.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the device.', - example: 'iPhone', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the user agent.', - example: 'Safari', - }, - { - name: 'original', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: 'Unparsed user_agent string.', - example: - 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15\n(KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1', - }, - { - name: 'os.family', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'OS family (such as redhat, debian, freebsd, windows).', - example: 'debian', - }, - { - name: 'os.full', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, including the version or code name.', - example: 'Mac OS Mojave', - }, - { - name: 'os.kernel', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system kernel version as a raw string.', - example: '4.4.0-112-generic', - }, - { - name: 'os.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, without the version.', - example: 'Mac OS X', - }, - { - name: 'os.platform', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'os.version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system version as a raw string.', - example: '10.14.1', - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Version of the user agent.', - example: 12, - }, - ], - }, - { - name: 'vlan', - title: 'VLAN', - group: 2, - description: - 'The VLAN fields are used to identify 802.1q tag(s) of a packet,\nas well as ingress and egress VLAN associations of an observer in relation to\na specific packet or connection.\n\nNetwork.vlan fields are used to record a single VLAN tag, or the outer tag in\nthe case of q-in-q encapsulations, for a packet or connection as observed, typically\nprovided by a network sensor (e.g. Zeek, Wireshark) passively reporting on traffic.\n\nNetwork.inner VLAN fields are used to report inner q-in-q 802.1q tags (multiple\n802.1q encapsulations) as observed, typically provided by a network sensor (e.g.\nZeek, Wireshark) passively reporting on traffic. Network.inner VLAN fields should\nonly be used in addition to network.vlan fields to indicate q-in-q tagging.\n\nObserver.ingress and observer.egress VLAN values are used to record observer\nspecific information when observer events contain discrete ingress and egress\nVLAN information, typically provided by firewalls, routers, or load balancers.', - type: 'group', - fields: [ - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'VLAN ID as reported by the observer.', - example: 10, - default_field: false, - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Optional VLAN name as reported by the observer.', - example: 'outside', - default_field: false, - }, - ], - }, - { - name: 'vulnerability', - title: 'Vulnerability', - group: 2, - description: - 'The vulnerability fields describe information about a vulnerability\nthat is relevant to an event.', - type: 'group', - fields: [ - { - name: 'category', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The type of system or architecture that the vulnerability affects.\nThese may be platform-specific (for example, Debian or SUSE) or general (for\nexample, Database or Firewall). For example (https://qualysguard.qualys.com/qwebhelp/fo_portal/knowledgebase/vulnerability_categories.htm[Qualys\nvulnerability categories])\n\nThis field must be an array.', - example: '["Firewall"]', - default_field: false, - }, - { - name: 'classification', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The classification of the vulnerability scoring system. For example\n(https://www.first.org/cvss/)', - example: 'CVSS', - default_field: false, - }, - { - name: 'description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: - 'The description of the vulnerability that provides additional context\nof the vulnerability. For example (https://cve.mitre.org/about/faqs.html#cve_entry_descriptions_created[Common\nVulnerabilities and Exposure CVE description])', - example: 'In macOS before 2.12.6, there is a vulnerability in the RPC...', - default_field: false, - }, - { - name: 'enumeration', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The type of identifier used for this vulnerability. For example\n(https://cve.mitre.org/about/)', - example: 'CVE', - default_field: false, - }, - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The identification (ID) is the number portion of a vulnerability\nentry. It includes a unique identification number for the vulnerability. For\nexample (https://cve.mitre.org/about/faqs.html#what_is_cve_id)[Common Vulnerabilities\nand Exposure CVE ID]', - example: 'CVE-2019-00001', - default_field: false, - }, - { - name: 'reference', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A resource that provides additional information, context, and mitigations\nfor the identified vulnerability.', - example: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-6111', - default_field: false, - }, - { - name: 'report_id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The report or scan identification number.', - example: 20191018.0001, - default_field: false, - }, - { - name: 'scanner.vendor', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The name of the vulnerability scanner vendor.', - example: 'Tenable', - default_field: false, - }, - { - name: 'score.base', - level: 'extended', - type: 'float', - description: - 'Scores can range from 0.0 to 10.0, with 10.0 being the most severe.\n\nBase scores cover an assessment for exploitability metrics (attack vector,\ncomplexity, privileges, and user interaction), impact metrics (confidentiality,\nintegrity, and availability), and scope. For example (https://www.first.org/cvss/specification-document)', - example: 5.5, - default_field: false, - }, - { - name: 'score.environmental', - level: 'extended', - type: 'float', - description: - 'Scores can range from 0.0 to 10.0, with 10.0 being the most severe.\n\nEnvironmental scores cover an assessment for any modified Base metrics, confidentiality,\nintegrity, and availability requirements. For example (https://www.first.org/cvss/specification-document)', - example: 5.5, - default_field: false, - }, - { - name: 'score.temporal', - level: 'extended', - type: 'float', - description: - 'Scores can range from 0.0 to 10.0, with 10.0 being the most severe.\n\nTemporal scores cover an assessment for code maturity, remediation level,\nand confidence. For example (https://www.first.org/cvss/specification-document)', - default_field: false, - }, - { - name: 'score.version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The National Vulnerability Database (NVD) provides qualitative\nseverity rankings of "Low", "Medium", and "High" for CVSS v2.0 base score\nranges in addition to the severity ratings for CVSS v3.0 as they are defined\nin the CVSS v3.0 specification.\n\nCVSS is owned and managed by FIRST.Org, Inc. (FIRST), a US-based non-profit\norganization, whose mission is to help computer security incident response\nteams across the world. For example (https://nvd.nist.gov/vuln-metrics/cvss)', - example: 2, - default_field: false, - }, - { - name: 'severity', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The severity of the vulnerability can help with metrics and internal\nprioritization regarding remediation. For example (https://nvd.nist.gov/vuln-metrics/cvss)', - example: 'Critical', - default_field: false, - }, - ], - }, - ], - }, - { - key: 'beat', - anchor: 'beat-common', - title: 'Beat', - description: 'Contains common beat fields available in all event types.\n', - fields: [ - { - name: 'agent.hostname', - type: 'keyword', - description: 'Hostname of the agent.', - }, - { - name: 'beat.timezone', - type: 'alias', - path: 'event.timezone', - migration: true, - }, - { - name: 'fields', - type: 'object', - object_type: 'keyword', - description: 'Contains user configurable fields.\n', - }, - { - name: 'beat.name', - type: 'alias', - path: 'host.name', - migration: true, - }, - { - name: 'beat.hostname', - type: 'alias', - path: 'agent.hostname', - migration: true, - }, - { - name: 'timeseries.instance', - type: 'keyword', - description: 'Time series instance id', - }, - ], - }, - { - key: 'cloud', - title: 'Cloud provider metadata', - description: 'Metadata from cloud providers added by the add_cloud_metadata processor.\n', - fields: [ - { - name: 'cloud.project.id', - example: 'project-x', - description: 'Name of the project in Google Cloud.\n', - }, - { - name: 'cloud.image.id', - example: 'ami-abcd1234', - description: 'Image ID for the cloud instance.\n', - }, - { - name: 'meta.cloud.provider', - type: 'alias', - path: 'cloud.provider', - migration: true, - }, - { - name: 'meta.cloud.instance_id', - type: 'alias', - path: 'cloud.instance.id', - migration: true, - }, - { - name: 'meta.cloud.instance_name', - type: 'alias', - path: 'cloud.instance.name', - migration: true, - }, - { - name: 'meta.cloud.machine_type', - type: 'alias', - path: 'cloud.machine.type', - migration: true, - }, - { - name: 'meta.cloud.availability_zone', - type: 'alias', - path: 'cloud.availability_zone', - migration: true, - }, - { - name: 'meta.cloud.project_id', - type: 'alias', - path: 'cloud.project.id', - migration: true, - }, - { - name: 'meta.cloud.region', - type: 'alias', - path: 'cloud.region', - migration: true, - }, - ], - }, - { - key: 'docker', - title: 'Docker', - description: 'Docker stats collected from Docker.\n', - short_config: false, - anchor: 'docker-processor', - fields: [ - { - name: 'docker', - type: 'group', - fields: [ - { - name: 'container.id', - type: 'alias', - path: 'container.id', - migration: true, - }, - { - name: 'container.image', - type: 'alias', - path: 'container.image.name', - migration: true, - }, - { - name: 'container.name', - type: 'alias', - path: 'container.name', - migration: true, - }, - { - name: 'container.labels', - type: 'object', - object_type: 'keyword', - description: 'Image labels.\n', - }, - ], - }, - ], - }, - { - key: 'host', - title: 'Host', - description: 'Info collected for the host machine.\n', - anchor: 'host-processor', - fields: [ - { - name: 'host', - type: 'group', - fields: [ - { - name: 'containerized', - type: 'boolean', - description: 'If the host is a container.\n', - }, - { - name: 'os.build', - type: 'keyword', - example: '18D109', - description: 'OS build information.\n', - }, - { - name: 'os.codename', - type: 'keyword', - example: 'stretch', - description: 'OS codename, if any.\n', - }, - ], - }, - ], - }, - { - key: 'kubernetes', - title: 'Kubernetes', - description: 'Kubernetes metadata added by the kubernetes processor\n', - short_config: false, - anchor: 'kubernetes-processor', - fields: [ - { - name: 'kubernetes', - type: 'group', - fields: [ - { - name: 'pod.name', - type: 'keyword', - description: 'Kubernetes pod name\n', - }, - { - name: 'pod.uid', - type: 'keyword', - description: 'Kubernetes Pod UID\n', - }, - { - name: 'namespace', - type: 'keyword', - description: 'Kubernetes namespace\n', - }, - { - name: 'node.name', - type: 'keyword', - description: 'Kubernetes node name\n', - }, - { - name: 'labels.*', - type: 'object', - object_type: 'keyword', - object_type_mapping_type: '*', - description: 'Kubernetes labels map\n', - }, - { - name: 'annotations.*', - type: 'object', - object_type: 'keyword', - object_type_mapping_type: '*', - description: 'Kubernetes annotations map\n', - }, - { - name: 'replicaset.name', - type: 'keyword', - description: 'Kubernetes replicaset name\n', - }, - { - name: 'deployment.name', - type: 'keyword', - description: 'Kubernetes deployment name\n', - }, - { - name: 'statefulset.name', - type: 'keyword', - description: 'Kubernetes statefulset name\n', - }, - { - name: 'container.name', - type: 'keyword', - description: 'Kubernetes container name\n', - }, - { - name: 'container.image', - type: 'keyword', - description: 'Kubernetes container image\n', - }, - ], - }, - ], - }, - { - key: 'process', - title: 'Process', - description: 'Process metadata fields\n', - fields: [ - { - name: 'process', - type: 'group', - fields: [ - { - name: 'exe', - type: 'alias', - path: 'process.executable', - migration: true, - }, - ], - }, - ], - }, - { - key: 'jolokia-autodiscover', - title: 'Jolokia Discovery autodiscover provider', - description: 'Metadata from Jolokia Discovery added by the jolokia provider.\n', - fields: [ - { - name: 'jolokia.agent.version', - type: 'keyword', - description: 'Version number of jolokia agent.\n', - }, - { - name: 'jolokia.agent.id', - type: 'keyword', - description: - 'Each agent has a unique id which can be either provided during startup of the agent in form of a configuration parameter or being autodetected. If autodected, the id has several parts: The IP, the process id, hashcode of the agent and its type.\n', - }, - { - name: 'jolokia.server.product', - type: 'keyword', - description: 'The container product if detected.\n', - }, - { - name: 'jolokia.server.version', - type: 'keyword', - description: "The container's version (if detected).\n", - }, - { - name: 'jolokia.server.vendor', - type: 'keyword', - description: 'The vendor of the container the agent is running in.\n', - }, - { - name: 'jolokia.url', - type: 'keyword', - description: 'The URL how this agent can be contacted.\n', - }, - { - name: 'jolokia.secured', - type: 'boolean', - description: 'Whether the agent was configured for authentication or not.\n', - }, - ], - }, - { - key: 'common', - title: 'Common', - description: 'Contains common fields available in all event types.\n', - fields: [ - { - name: 'file', - type: 'group', - description: 'File attributes.', - fields: [ - { - name: 'setuid', - type: 'boolean', - example: true, - description: 'Set if the file has the `setuid` bit set. Omitted otherwise.', - }, - { - name: 'setgid', - type: 'boolean', - example: true, - description: 'Set if the file has the `setgid` bit set. Omitted otherwise.', - }, - { - name: 'origin', - type: 'keyword', - description: - 'An array of strings describing a possible external origin for this file. For example, the URL it was downloaded from. Only supported in macOS, via the kMDItemWhereFroms attribute. Omitted if origin information is not available.\n', - multi_fields: [ - { - name: 'raw', - type: 'keyword', - description: - 'This is a non-analyzed field that is useful for aggregations on the origin data.\n', - }, - ], - }, - { - name: 'selinux', - type: 'group', - description: 'The SELinux identity of the file.', - fields: [ - { - name: 'user', - type: 'keyword', - description: 'The owner of the object.', - }, - { - name: 'role', - type: 'keyword', - description: "The object's SELinux role.", - }, - { - name: 'domain', - type: 'keyword', - description: "The object's SELinux domain or type.", - }, - { - name: 'level', - type: 'keyword', - example: 's0', - description: "The object's SELinux level.", - }, - ], - }, - ], - }, - { - name: 'user', - type: 'group', - description: 'User information.', - fields: [ - { - name: 'audit', - type: 'group', - description: 'Audit user information.', - fields: [ - { - name: 'id', - type: 'keyword', - description: 'Audit user ID.', - }, - { - name: 'name', - type: 'keyword', - description: 'Audit user name.', - }, - ], - }, - { - name: 'effective', - type: 'group', - description: 'Effective user information.', - fields: [ - { - name: 'id', - type: 'keyword', - description: 'Effective user ID.', - }, - { - name: 'name', - type: 'keyword', - description: 'Effective user name.', - }, - { - name: 'group', - type: 'group', - description: 'Effective group information.', - fields: [ - { - name: 'id', - type: 'keyword', - description: 'Effective group ID.', - }, - { - name: 'name', - type: 'keyword', - description: 'Effective group name.', - }, - ], - }, - ], - }, - { - name: 'filesystem', - type: 'group', - description: 'Filesystem user information.', - fields: [ - { - name: 'id', - type: 'keyword', - description: 'Filesystem user ID.', - }, - { - name: 'name', - type: 'keyword', - description: 'Filesystem user name.', - }, - { - name: 'group', - type: 'group', - description: 'Filesystem group information.', - fields: [ - { - name: 'id', - type: 'keyword', - description: 'Filesystem group ID.', - }, - { - name: 'name', - type: 'keyword', - description: 'Filesystem group name.', - }, - ], - }, - ], - }, - { - name: 'saved', - type: 'group', - description: 'Saved user information.', - fields: [ - { - name: 'id', - type: 'keyword', - description: 'Saved user ID.', - }, - { - name: 'name', - type: 'keyword', - description: 'Saved user name.', - }, - { - name: 'group', - type: 'group', - description: 'Saved group information.', - fields: [ - { - name: 'id', - type: 'keyword', - description: 'Saved group ID.', - }, - { - name: 'name', - type: 'keyword', - description: 'Saved group name.', - }, - ], - }, - ], - }, - ], - }, - ], - }, - { - key: 'auditd', - title: 'Auditd', - description: 'These are the fields generated by the auditd module.', - fields: [ - { - name: 'user', - type: 'group', - fields: [ - { - name: 'auid', - type: 'alias', - path: 'user.audit.id', - migration: true, - }, - { - name: 'uid', - type: 'alias', - path: 'user.id', - migration: true, - }, - { - name: 'euid', - type: 'alias', - path: 'user.effective.id', - migration: true, - }, - { - name: 'fsuid', - type: 'alias', - path: 'user.filesystem.id', - migration: true, - }, - { - name: 'suid', - type: 'alias', - path: 'user.saved.id', - migration: true, - }, - { - name: 'gid', - type: 'alias', - path: 'user.group.id', - migration: true, - }, - { - name: 'egid', - type: 'alias', - path: 'user.effective.group.id', - migration: true, - }, - { - name: 'sgid', - type: 'alias', - path: 'user.saved.group.id', - migration: true, - }, - { - name: 'fsgid', - type: 'alias', - path: 'user.filesystem.group.id', - migration: true, - }, - { - name: 'name_map', - type: 'group', - description: - 'If `resolve_ids` is set to true in the configuration then `name_map` will contain a mapping of uid field names to the resolved name (e.g. auid -> root).\n', - fields: [ - { - name: 'auid', - type: 'alias', - path: 'user.audit.name', - migration: true, - }, - { - name: 'uid', - type: 'alias', - path: 'user.name', - migration: true, - }, - { - name: 'euid', - type: 'alias', - path: 'user.effective.name', - migration: true, - }, - { - name: 'fsuid', - type: 'alias', - path: 'user.filesystem.name', - migration: true, - }, - { - name: 'suid', - type: 'alias', - path: 'user.saved.name', - migration: true, - }, - { - name: 'gid', - type: 'alias', - path: 'user.group.name', - migration: true, - }, - { - name: 'egid', - type: 'alias', - path: 'user.effective.group.name', - migration: true, - }, - { - name: 'sgid', - type: 'alias', - path: 'user.saved.group.name', - migration: true, - }, - { - name: 'fsgid', - type: 'alias', - path: 'user.filesystem.group.name', - migration: true, - }, - ], - }, - { - name: 'selinux', - type: 'group', - description: 'The SELinux identity of the actor.', - fields: [ - { - name: 'user', - type: 'keyword', - description: 'account submitted for authentication', - }, - { - name: 'role', - type: 'keyword', - description: "user's SELinux role", - }, - { - name: 'domain', - type: 'keyword', - description: "The actor's SELinux domain or type.", - }, - { - name: 'level', - type: 'keyword', - example: 's0', - description: "The actor's SELinux level.", - }, - { - name: 'category', - type: 'keyword', - description: "The actor's SELinux category or compartments.", - }, - ], - }, - ], - }, - { - name: 'process', - type: 'group', - description: 'Process attributes.', - fields: [ - { - name: 'cwd', - type: 'alias', - path: 'process.working_directory', - migration: true, - description: 'The current working directory.', - }, - ], - }, - { - name: 'source', - type: 'group', - description: 'Source that triggered the event.', - fields: [ - { - name: 'path', - type: 'keyword', - description: 'This is the path associated with a unix socket.', - }, - ], - }, - { - name: 'destination', - type: 'group', - description: 'Destination address that triggered the event.', - fields: [ - { - name: 'path', - type: 'keyword', - description: 'This is the path associated with a unix socket.', - }, - ], - }, - { - name: 'auditd', - type: 'group', - fields: [ - { - name: 'message_type', - type: 'keyword', - example: 'syscall', - description: 'The audit message type (e.g. syscall or apparmor_denied).\n', - }, - { - name: 'sequence', - type: 'long', - description: - 'The sequence number of the event as assigned by the kernel. Sequence numbers are stored as a uint32 in the kernel and can rollover.\n', - }, - { - name: 'session', - type: 'keyword', - description: - 'The session ID assigned to a login. All events related to a login session will have the same value.\n', - }, - { - name: 'result', - type: 'keyword', - example: 'success or fail', - description: 'The result of the audited operation (success/fail).', - }, - { - name: 'summary', - type: 'group', - fields: [ - { - name: 'actor', - type: 'group', - description: 'The actor is the user that triggered the audit event.', - fields: [ - { - name: 'primary', - type: 'keyword', - description: - "The primary identity of the actor. This is the actor's original login ID. It will not change even if the user changes to another account.\n", - }, - { - name: 'secondary', - type: 'keyword', - description: - 'The secondary identity of the actor. This is typically\nthe same as the primary, except for when the user has used `su`.', - }, - ], - }, - { - name: 'object', - type: 'group', - description: 'This is the thing or object being acted upon in the event.\n', - fields: [ - { - name: 'type', - type: 'keyword', - description: - 'A description of the what the "thing" is (e.g. file, socket, user-session).\n', - }, - { - name: 'primary', - type: 'keyword', - description: '', - }, - { - name: 'secondary', - type: 'keyword', - description: '', - }, - ], - }, - { - name: 'how', - type: 'keyword', - description: - 'This describes how the action was performed. Usually this is the exe or command that was being executed that triggered the event.\n', - }, - ], - }, - { - name: 'paths', - type: 'group', - description: 'List of paths associated with the event.', - fields: [ - { - name: 'inode', - type: 'keyword', - description: 'inode number', - }, - { - name: 'dev', - type: 'keyword', - description: 'device name as found in /dev', - }, - { - name: 'obj_user', - type: 'keyword', - description: '', - }, - { - name: 'obj_role', - type: 'keyword', - description: '', - }, - { - name: 'obj_domain', - type: 'keyword', - description: '', - }, - { - name: 'obj_level', - type: 'keyword', - description: '', - }, - { - name: 'objtype', - type: 'keyword', - description: '', - }, - { - name: 'ouid', - type: 'keyword', - description: 'file owner user ID', - }, - { - name: 'rdev', - type: 'keyword', - description: 'the device identifier (special files only)', - }, - { - name: 'nametype', - type: 'keyword', - description: 'kind of file operation being referenced', - }, - { - name: 'ogid', - type: 'keyword', - description: 'file owner group ID', - }, - { - name: 'item', - type: 'keyword', - description: 'which item is being recorded', - }, - { - name: 'mode', - type: 'keyword', - description: 'mode flags on a file', - }, - { - name: 'name', - type: 'keyword', - description: 'file name in avcs', - }, - ], - }, - { - name: 'data', - type: 'group', - description: 'The data from the audit messages.', - fields: [ - { - name: 'action', - type: 'keyword', - description: 'netfilter packet disposition', - }, - { - name: 'minor', - type: 'keyword', - description: 'device minor number', - }, - { - name: 'acct', - type: 'keyword', - description: "a user's account name", - }, - { - name: 'addr', - type: 'keyword', - description: 'the remote address that the user is connecting from', - }, - { - name: 'cipher', - type: 'keyword', - description: 'name of crypto cipher selected', - }, - { - name: 'id', - type: 'keyword', - description: 'during account changes', - }, - { - name: 'entries', - type: 'keyword', - description: 'number of entries in the netfilter table', - }, - { - name: 'kind', - type: 'keyword', - description: 'server or client in crypto operation', - }, - { - name: 'ksize', - type: 'keyword', - description: 'key size for crypto operation', - }, - { - name: 'spid', - type: 'keyword', - description: 'sent process ID', - }, - { - name: 'arch', - type: 'keyword', - description: 'the elf architecture flags', - }, - { - name: 'argc', - type: 'keyword', - description: 'the number of arguments to an execve syscall', - }, - { - name: 'major', - type: 'keyword', - description: 'device major number', - }, - { - name: 'unit', - type: 'keyword', - description: 'systemd unit', - }, - { - name: 'table', - type: 'keyword', - description: 'netfilter table name', - }, - { - name: 'terminal', - type: 'keyword', - description: 'terminal name the user is running programs on', - }, - { - name: 'grantors', - type: 'keyword', - description: 'pam modules approving the action', - }, - { - name: 'direction', - type: 'keyword', - description: 'direction of crypto operation', - }, - { - name: 'op', - type: 'keyword', - description: 'the operation being performed that is audited', - }, - { - name: 'tty', - type: 'keyword', - description: 'tty udevice the user is running programs on', - }, - { - name: 'syscall', - type: 'keyword', - description: 'syscall number in effect when the event occurred', - }, - { - name: 'data', - type: 'keyword', - description: 'TTY text', - }, - { - name: 'family', - type: 'keyword', - description: 'netfilter protocol', - }, - { - name: 'mac', - type: 'keyword', - description: 'crypto MAC algorithm selected', - }, - { - name: 'pfs', - type: 'keyword', - description: 'perfect forward secrecy method', - }, - { - name: 'items', - type: 'keyword', - description: 'the number of path records in the event', - }, - { - name: 'a0', - type: 'keyword', - description: '', - }, - { - name: 'a1', - type: 'keyword', - description: '', - }, - { - name: 'a2', - type: 'keyword', - description: '', - }, - { - name: 'a3', - type: 'keyword', - description: '', - }, - { - name: 'hostname', - type: 'keyword', - description: 'the hostname that the user is connecting from', - }, - { - name: 'lport', - type: 'keyword', - description: 'local network port', - }, - { - name: 'rport', - type: 'keyword', - description: 'remote port number', - }, - { - name: 'exit', - type: 'keyword', - description: 'syscall exit code', - }, - { - name: 'fp', - type: 'keyword', - description: 'crypto key finger print', - }, - { - name: 'laddr', - type: 'keyword', - description: 'local network address', - }, - { - name: 'sport', - type: 'keyword', - description: 'local port number', - }, - { - name: 'capability', - type: 'keyword', - description: 'posix capabilities', - }, - { - name: 'nargs', - type: 'keyword', - description: 'the number of arguments to a socket call', - }, - { - name: 'new-enabled', - type: 'keyword', - description: 'new TTY audit enabled setting', - }, - { - name: 'audit_backlog_limit', - type: 'keyword', - description: "audit system's backlog queue size", - }, - { - name: 'dir', - type: 'keyword', - description: 'directory name', - }, - { - name: 'cap_pe', - type: 'keyword', - description: 'process effective capability map', - }, - { - name: 'model', - type: 'keyword', - description: 'security model being used for virt', - }, - { - name: 'new_pp', - type: 'keyword', - description: 'new process permitted capability map', - }, - { - name: 'old-enabled', - type: 'keyword', - description: 'present TTY audit enabled setting', - }, - { - name: 'oauid', - type: 'keyword', - description: "object's login user ID", - }, - { - name: 'old', - type: 'keyword', - description: 'old value', - }, - { - name: 'banners', - type: 'keyword', - description: 'banners used on printed page', - }, - { - name: 'feature', - type: 'keyword', - description: 'kernel feature being changed', - }, - { - name: 'vm-ctx', - type: 'keyword', - description: "the vm's context string", - }, - { - name: 'opid', - type: 'keyword', - description: "object's process ID", - }, - { - name: 'seperms', - type: 'keyword', - description: 'SELinux permissions being used', - }, - { - name: 'seresult', - type: 'keyword', - description: 'SELinux AVC decision granted/denied', - }, - { - name: 'new-rng', - type: 'keyword', - description: 'device name of rng being added from a vm', - }, - { - name: 'old-net', - type: 'keyword', - description: 'present MAC address assigned to vm', - }, - { - name: 'sigev_signo', - type: 'keyword', - description: 'signal number', - }, - { - name: 'ino', - type: 'keyword', - description: 'inode number', - }, - { - name: 'old_enforcing', - type: 'keyword', - description: 'old MAC enforcement status', - }, - { - name: 'old-vcpu', - type: 'keyword', - description: 'present number of CPU cores', - }, - { - name: 'range', - type: 'keyword', - description: "user's SE Linux range", - }, - { - name: 'res', - type: 'keyword', - description: 'result of the audited operation(success/fail)', - }, - { - name: 'added', - type: 'keyword', - description: 'number of new files detected', - }, - { - name: 'fam', - type: 'keyword', - description: 'socket address family', - }, - { - name: 'nlnk-pid', - type: 'keyword', - description: 'pid of netlink packet sender', - }, - { - name: 'subj', - type: 'keyword', - description: "lspp subject's context string", - }, - { - name: 'a[0-3]', - type: 'keyword', - description: 'the arguments to a syscall', - }, - { - name: 'cgroup', - type: 'keyword', - description: 'path to cgroup in sysfs', - }, - { - name: 'kernel', - type: 'keyword', - description: "kernel's version number", - }, - { - name: 'ocomm', - type: 'keyword', - description: "object's command line name", - }, - { - name: 'new-net', - type: 'keyword', - description: 'MAC address being assigned to vm', - }, - { - name: 'permissive', - type: 'keyword', - description: 'SELinux is in permissive mode', - }, - { - name: 'class', - type: 'keyword', - description: 'resource class assigned to vm', - }, - { - name: 'compat', - type: 'keyword', - description: 'is_compat_task result', - }, - { - name: 'fi', - type: 'keyword', - description: 'file assigned inherited capability map', - }, - { - name: 'changed', - type: 'keyword', - description: 'number of changed files', - }, - { - name: 'msg', - type: 'keyword', - description: 'the payload of the audit record', - }, - { - name: 'dport', - type: 'keyword', - description: 'remote port number', - }, - { - name: 'new-seuser', - type: 'keyword', - description: 'new SELinux user', - }, - { - name: 'invalid_context', - type: 'keyword', - description: 'SELinux context', - }, - { - name: 'dmac', - type: 'keyword', - description: 'remote MAC address', - }, - { - name: 'ipx-net', - type: 'keyword', - description: 'IPX network number', - }, - { - name: 'iuid', - type: 'keyword', - description: "ipc object's user ID", - }, - { - name: 'macproto', - type: 'keyword', - description: 'ethernet packet type ID field', - }, - { - name: 'obj', - type: 'keyword', - description: 'lspp object context string', - }, - { - name: 'ipid', - type: 'keyword', - description: 'IP datagram fragment identifier', - }, - { - name: 'new-fs', - type: 'keyword', - description: 'file system being added to vm', - }, - { - name: 'vm-pid', - type: 'keyword', - description: "vm's process ID", - }, - { - name: 'cap_pi', - type: 'keyword', - description: 'process inherited capability map', - }, - { - name: 'old-auid', - type: 'keyword', - description: 'previous auid value', - }, - { - name: 'oses', - type: 'keyword', - description: "object's session ID", - }, - { - name: 'fd', - type: 'keyword', - description: 'file descriptor number', - }, - { - name: 'igid', - type: 'keyword', - description: "ipc object's group ID", - }, - { - name: 'new-disk', - type: 'keyword', - description: 'disk being added to vm', - }, - { - name: 'parent', - type: 'keyword', - description: 'the inode number of the parent file', - }, - { - name: 'len', - type: 'keyword', - description: 'length', - }, - { - name: 'oflag', - type: 'keyword', - description: 'open syscall flags', - }, - { - name: 'uuid', - type: 'keyword', - description: 'a UUID', - }, - { - name: 'code', - type: 'keyword', - description: 'seccomp action code', - }, - { - name: 'nlnk-grp', - type: 'keyword', - description: 'netlink group number', - }, - { - name: 'cap_fp', - type: 'keyword', - description: 'file permitted capability map', - }, - { - name: 'new-mem', - type: 'keyword', - description: 'new amount of memory in KB', - }, - { - name: 'seperm', - type: 'keyword', - description: 'SELinux permission being decided on', - }, - { - name: 'enforcing', - type: 'keyword', - description: 'new MAC enforcement status', - }, - { - name: 'new-chardev', - type: 'keyword', - description: 'new character device being assigned to vm', - }, - { - name: 'old-rng', - type: 'keyword', - description: 'device name of rng being removed from a vm', - }, - { - name: 'outif', - type: 'keyword', - description: 'out interface number', - }, - { - name: 'cmd', - type: 'keyword', - description: 'command being executed', - }, - { - name: 'hook', - type: 'keyword', - description: 'netfilter hook that packet came from', - }, - { - name: 'new-level', - type: 'keyword', - description: 'new run level', - }, - { - name: 'sauid', - type: 'keyword', - description: 'sent login user ID', - }, - { - name: 'sig', - type: 'keyword', - description: 'signal number', - }, - { - name: 'audit_backlog_wait_time', - type: 'keyword', - description: "audit system's backlog wait time", - }, - { - name: 'printer', - type: 'keyword', - description: 'printer name', - }, - { - name: 'old-mem', - type: 'keyword', - description: 'present amount of memory in KB', - }, - { - name: 'perm', - type: 'keyword', - description: 'the file permission being used', - }, - { - name: 'old_pi', - type: 'keyword', - description: 'old process inherited capability map', - }, - { - name: 'state', - type: 'keyword', - description: 'audit daemon configuration resulting state', - }, - { - name: 'format', - type: 'keyword', - description: "audit log's format", - }, - { - name: 'new_gid', - type: 'keyword', - description: 'new group ID being assigned', - }, - { - name: 'tcontext', - type: 'keyword', - description: "the target's or object's context string", - }, - { - name: 'maj', - type: 'keyword', - description: 'device major number', - }, - { - name: 'watch', - type: 'keyword', - description: 'file name in a watch record', - }, - { - name: 'device', - type: 'keyword', - description: 'device name', - }, - { - name: 'grp', - type: 'keyword', - description: 'group name', - }, - { - name: 'bool', - type: 'keyword', - description: 'name of SELinux boolean', - }, - { - name: 'icmp_type', - type: 'keyword', - description: 'type of icmp message', - }, - { - name: 'new_lock', - type: 'keyword', - description: 'new value of feature lock', - }, - { - name: 'old_prom', - type: 'keyword', - description: 'network promiscuity flag', - }, - { - name: 'acl', - type: 'keyword', - description: 'access mode of resource assigned to vm', - }, - { - name: 'ip', - type: 'keyword', - description: 'network address of a printer', - }, - { - name: 'new_pi', - type: 'keyword', - description: 'new process inherited capability map', - }, - { - name: 'default-context', - type: 'keyword', - description: 'default MAC context', - }, - { - name: 'inode_gid', - type: 'keyword', - description: "group ID of the inode's owner", - }, - { - name: 'new-log_passwd', - type: 'keyword', - description: 'new value for TTY password logging', - }, - { - name: 'new_pe', - type: 'keyword', - description: 'new process effective capability map', - }, - { - name: 'selected-context', - type: 'keyword', - description: 'new MAC context assigned to session', - }, - { - name: 'cap_fver', - type: 'keyword', - description: 'file system capabilities version number', - }, - { - name: 'file', - type: 'keyword', - description: 'file name', - }, - { - name: 'net', - type: 'keyword', - description: 'network MAC address', - }, - { - name: 'virt', - type: 'keyword', - description: 'kind of virtualization being referenced', - }, - { - name: 'cap_pp', - type: 'keyword', - description: 'process permitted capability map', - }, - { - name: 'old-range', - type: 'keyword', - description: 'present SELinux range', - }, - { - name: 'resrc', - type: 'keyword', - description: 'resource being assigned', - }, - { - name: 'new-range', - type: 'keyword', - description: 'new SELinux range', - }, - { - name: 'obj_gid', - type: 'keyword', - description: 'group ID of object', - }, - { - name: 'proto', - type: 'keyword', - description: 'network protocol', - }, - { - name: 'old-disk', - type: 'keyword', - description: 'disk being removed from vm', - }, - { - name: 'audit_failure', - type: 'keyword', - description: "audit system's failure mode", - }, - { - name: 'inif', - type: 'keyword', - description: 'in interface number', - }, - { - name: 'vm', - type: 'keyword', - description: 'virtual machine name', - }, - { - name: 'flags', - type: 'keyword', - description: 'mmap syscall flags', - }, - { - name: 'nlnk-fam', - type: 'keyword', - description: 'netlink protocol number', - }, - { - name: 'old-fs', - type: 'keyword', - description: 'file system being removed from vm', - }, - { - name: 'old-ses', - type: 'keyword', - description: 'previous ses value', - }, - { - name: 'seqno', - type: 'keyword', - description: 'sequence number', - }, - { - name: 'fver', - type: 'keyword', - description: 'file system capabilities version number', - }, - { - name: 'qbytes', - type: 'keyword', - description: 'ipc objects quantity of bytes', - }, - { - name: 'seuser', - type: 'keyword', - description: "user's SE Linux user acct", - }, - { - name: 'cap_fe', - type: 'keyword', - description: 'file assigned effective capability map', - }, - { - name: 'new-vcpu', - type: 'keyword', - description: 'new number of CPU cores', - }, - { - name: 'old-level', - type: 'keyword', - description: 'old run level', - }, - { - name: 'old_pp', - type: 'keyword', - description: 'old process permitted capability map', - }, - { - name: 'daddr', - type: 'keyword', - description: 'remote IP address', - }, - { - name: 'old-role', - type: 'keyword', - description: 'present SELinux role', - }, - { - name: 'ioctlcmd', - type: 'keyword', - description: 'The request argument to the ioctl syscall', - }, - { - name: 'smac', - type: 'keyword', - description: 'local MAC address', - }, - { - name: 'apparmor', - type: 'keyword', - description: 'apparmor event information', - }, - { - name: 'fe', - type: 'keyword', - description: 'file assigned effective capability map', - }, - { - name: 'perm_mask', - type: 'keyword', - description: 'file permission mask that triggered a watch event', - }, - { - name: 'ses', - type: 'keyword', - description: 'login session ID', - }, - { - name: 'cap_fi', - type: 'keyword', - description: 'file inherited capability map', - }, - { - name: 'obj_uid', - type: 'keyword', - description: 'user ID of object', - }, - { - name: 'reason', - type: 'keyword', - description: 'text string denoting a reason for the action', - }, - { - name: 'list', - type: 'keyword', - description: "the audit system's filter list number", - }, - { - name: 'old_lock', - type: 'keyword', - description: 'present value of feature lock', - }, - { - name: 'bus', - type: 'keyword', - description: 'name of subsystem bus a vm resource belongs to', - }, - { - name: 'old_pe', - type: 'keyword', - description: 'old process effective capability map', - }, - { - name: 'new-role', - type: 'keyword', - description: 'new SELinux role', - }, - { - name: 'prom', - type: 'keyword', - description: 'network promiscuity flag', - }, - { - name: 'uri', - type: 'keyword', - description: 'URI pointing to a printer', - }, - { - name: 'audit_enabled', - type: 'keyword', - description: "audit systems's enable/disable status", - }, - { - name: 'old-log_passwd', - type: 'keyword', - description: 'present value for TTY password logging', - }, - { - name: 'old-seuser', - type: 'keyword', - description: 'present SELinux user', - }, - { - name: 'per', - type: 'keyword', - description: 'linux personality', - }, - { - name: 'scontext', - type: 'keyword', - description: "the subject's context string", - }, - { - name: 'tclass', - type: 'keyword', - description: "target's object classification", - }, - { - name: 'ver', - type: 'keyword', - description: "audit daemon's version number", - }, - { - name: 'new', - type: 'keyword', - description: 'value being set in feature', - }, - { - name: 'val', - type: 'keyword', - description: 'generic value associated with the operation', - }, - { - name: 'img-ctx', - type: 'keyword', - description: "the vm's disk image context string", - }, - { - name: 'old-chardev', - type: 'keyword', - description: 'present character device assigned to vm', - }, - { - name: 'old_val', - type: 'keyword', - description: 'current value of SELinux boolean', - }, - { - name: 'success', - type: 'keyword', - description: 'whether the syscall was successful or not', - }, - { - name: 'inode_uid', - type: 'keyword', - description: "user ID of the inode's owner", - }, - { - name: 'removed', - type: 'keyword', - description: 'number of deleted files', - }, - { - name: 'socket', - type: 'group', - fields: [ - { - name: 'port', - type: 'keyword', - description: 'The port number.', - }, - { - name: 'saddr', - type: 'keyword', - description: 'The raw socket address structure.', - }, - { - name: 'addr', - type: 'keyword', - description: 'The remote address.', - }, - { - name: 'family', - type: 'keyword', - example: 'unix', - description: 'The socket family (unix, ipv4, ipv6, netlink).', - }, - { - name: 'path', - type: 'keyword', - description: 'This is the path associated with a unix socket.', - }, - ], - }, - ], - }, - { - name: 'messages', - type: 'alias', - migration: true, - path: 'event.original', - description: - 'An ordered list of the raw messages received from the kernel that were used to construct this document. This field is present if an error occurred processing the data or if `include_raw_message` is set in the config.\n', - }, - { - name: 'warnings', - type: 'alias', - migration: true, - path: 'error.message', - description: - 'The warnings generated by the Beat during the construction of the event. These are disabled by default and are used for development and debug purposes only.\n', - }, - ], - }, - { - name: 'geoip', - type: 'group', - description: - 'The geoip fields are defined as a convenience in case you decide to enrich the data using a geoip filter in Logstash or Ingest Node.\n', - fields: [ - { - name: 'continent_name', - type: 'keyword', - description: 'The name of the continent.\n', - }, - { - name: 'city_name', - type: 'keyword', - description: 'The name of the city.\n', - }, - { - name: 'region_name', - type: 'keyword', - description: 'The name of the region.\n', - }, - { - name: 'country_iso_code', - type: 'keyword', - description: 'Country ISO code.\n', - }, - { - name: 'location', - type: 'geo_point', - description: 'The longitude and latitude.\n', - }, - ], - }, - ], - }, - { - key: 'file_integrity', - title: 'File Integrity', - description: 'These are the fields generated by the file_integrity module.', - fields: [ - { - name: 'hash', - type: 'group', - description: - 'Hashes of the file. The keys are algorithm names and the values are the hex encoded digest values.\n', - fields: [ - { - name: 'blake2b_256', - type: 'keyword', - description: 'BLAKE2b-256 hash of the file.', - }, - { - name: 'blake2b_384', - type: 'keyword', - description: 'BLAKE2b-384 hash of the file.', - }, - { - name: 'blake2b_512', - type: 'keyword', - description: 'BLAKE2b-512 hash of the file.', - }, - { - name: 'md5', - overwrite: true, - type: 'keyword', - description: 'MD5 hash of the file.', - }, - { - name: 'sha1', - overwrite: true, - type: 'keyword', - description: 'SHA1 hash of the file.', - }, - { - name: 'sha224', - type: 'keyword', - description: 'SHA224 hash of the file.', - }, - { - name: 'sha256', - overwrite: true, - type: 'keyword', - description: 'SHA256 hash of the file.', - }, - { - name: 'sha384', - type: 'keyword', - description: 'SHA384 hash of the file.', - }, - { - name: 'sha3_224', - type: 'keyword', - description: 'SHA3_224 hash of the file.', - }, - { - name: 'sha3_256', - type: 'keyword', - description: 'SHA3_256 hash of the file.', - }, - { - name: 'sha3_384', - type: 'keyword', - description: 'SHA3_384 hash of the file.', - }, - { - name: 'sha3_512', - type: 'keyword', - description: 'SHA3_512 hash of the file.', - }, - { - name: 'sha512', - overwrite: true, - type: 'keyword', - description: 'SHA512 hash of the file.', - }, - { - name: 'sha512_224', - type: 'keyword', - description: 'SHA512/224 hash of the file.', - }, - { - name: 'sha512_256', - type: 'keyword', - description: 'SHA512/256 hash of the file.', - }, - { - name: 'xxh64', - type: 'keyword', - description: 'XX64 hash of the file.', - }, - ], - }, - ], - }, - { - key: 'system', - title: 'System', - description: 'These are the fields generated by the system module.\n', - release: 'beta', - fields: [ - { - name: 'event', - type: 'group', - fields: [ - { - name: 'origin', - type: 'keyword', - description: - 'Origin of the event. This can be a file path (e.g. `/var/log/log.1`), or the name of the system component that supplied the data (e.g. `netlink`).\n', - }, - ], - }, - { - name: 'user', - type: 'group', - fields: [ - { - name: 'entity_id', - type: 'keyword', - description: - 'ID uniquely identifying the user on a host. It is computed as a SHA-256 hash of the host ID, user ID, and user name.\n', - }, - { - name: 'terminal', - type: 'keyword', - description: 'Terminal of the user.\n', - }, - ], - }, - { - name: 'process', - type: 'group', - fields: [ - { - name: 'hash', - type: 'group', - description: - 'Hashes of the executable. The keys are algorithm names and the values are the hex encoded digest values.\n', - fields: [ - { - name: 'blake2b_256', - type: 'keyword', - description: 'BLAKE2b-256 hash of the executable.', - }, - { - name: 'blake2b_384', - type: 'keyword', - description: 'BLAKE2b-384 hash of the executable.', - }, - { - name: 'blake2b_512', - type: 'keyword', - description: 'BLAKE2b-512 hash of the executable.', - }, - { - name: 'sha224', - type: 'keyword', - description: 'SHA224 hash of the executable.', - }, - { - name: 'sha384', - type: 'keyword', - description: 'SHA384 hash of the executable.', - }, - { - name: 'sha3_224', - type: 'keyword', - description: 'SHA3_224 hash of the executable.', - }, - { - name: 'sha3_256', - type: 'keyword', - description: 'SHA3_256 hash of the executable.', - }, - { - name: 'sha3_384', - type: 'keyword', - description: 'SHA3_384 hash of the executable.', - }, - { - name: 'sha3_512', - type: 'keyword', - description: 'SHA3_512 hash of the executable.', - }, - { - name: 'sha512_224', - type: 'keyword', - description: 'SHA512/224 hash of the executable.', - }, - { - name: 'sha512_256', - type: 'keyword', - description: 'SHA512/256 hash of the executable.', - }, - { - name: 'xxh64', - type: 'keyword', - description: 'XX64 hash of the executable.', - }, - ], - }, - ], - }, - { - name: 'socket', - type: 'group', - fields: [ - { - name: 'entity_id', - type: 'keyword', - description: - 'ID uniquely identifying the socket. It is computed as a SHA-256 hash of the host ID, socket inode, local IP, local port, remote IP, and remote port.\n', - }, - ], - }, - { - name: 'system.audit', - type: 'group', - description: '\n', - fields: [ - { - name: 'host', - type: 'group', - description: '`host` contains general host information.\n', - release: 'beta', - fields: [ - { - name: 'uptime', - type: 'long', - format: 'duration', - input_format: 'nanoseconds', - output_format: 'asDays', - output_precision: 1, - description: 'Uptime in nanoseconds.\n', - }, - { - name: 'boottime', - type: 'date', - description: 'Boot time.\n', - }, - { - name: 'containerized', - type: 'boolean', - description: 'Set if host is a container.\n', - }, - { - name: 'timezone.name', - type: 'keyword', - description: 'Name of the timezone of the host, e.g. BST.\n', - }, - { - name: 'timezone.offset.sec', - type: 'long', - description: 'Timezone offset in seconds.\n', - }, - { - name: 'hostname', - type: 'keyword', - description: 'Hostname.\n', - }, - { - name: 'id', - type: 'keyword', - description: 'Host ID.\n', - }, - { - name: 'architecture', - type: 'keyword', - description: 'Host architecture (e.g. x86_64).\n', - }, - { - name: 'mac', - type: 'keyword', - description: 'MAC addresses.\n', - }, - { - name: 'ip', - type: 'ip', - description: 'IP addresses.\n', - }, - { - name: 'os', - type: 'group', - description: '`os` contains information about the operating system.\n', - fields: [ - { - name: 'codename', - type: 'keyword', - description: 'OS codename, if any (e.g. stretch).\n', - }, - { - name: 'platform', - type: 'keyword', - description: 'OS platform (e.g. centos, ubuntu, windows).\n', - }, - { - name: 'name', - type: 'keyword', - description: 'OS name (e.g. Mac OS X).\n', - }, - { - name: 'family', - type: 'keyword', - description: 'OS family (e.g. redhat, debian, freebsd, windows).\n', - }, - { - name: 'version', - type: 'keyword', - description: 'OS version.\n', - }, - { - name: 'kernel', - type: 'keyword', - description: "The operating system's kernel version.\n", - }, - ], - }, - ], - }, - { - name: 'package', - type: 'group', - description: '`package` contains information about an installed or removed package.\n', - release: 'beta', - fields: [ - { - name: 'entity_id', - type: 'keyword', - description: - 'ID uniquely identifying the package. It is computed as a SHA-256 hash of the host ID, package name, and package version.\n', - }, - { - name: 'name', - type: 'keyword', - description: 'Package name.\n', - }, - { - name: 'version', - type: 'keyword', - description: 'Package version.\n', - }, - { - name: 'release', - type: 'keyword', - description: 'Package release.\n', - }, - { - name: 'arch', - type: 'keyword', - description: 'Package architecture.\n', - }, - { - name: 'license', - type: 'keyword', - description: 'Package license.\n', - }, - { - name: 'installtime', - type: 'date', - description: 'Package install time.\n', - }, - { - name: 'size', - type: 'long', - description: 'Package size.\n', - }, - { - name: 'summary', - description: 'Package summary.\n', - }, - { - name: 'url', - type: 'keyword', - description: 'Package URL.\n', - }, - ], - }, - { - name: 'user', - type: 'group', - description: '`user` contains information about the users on a system.\n', - release: 'beta', - fields: [ - { - name: 'name', - type: 'keyword', - description: 'User name.\n', - }, - { - name: 'uid', - type: 'keyword', - description: 'User ID.\n', - }, - { - name: 'gid', - type: 'keyword', - description: 'Group ID.\n', - }, - { - name: 'dir', - type: 'keyword', - description: "User's home directory.\n", - }, - { - name: 'shell', - type: 'keyword', - description: 'Program to run at login.\n', - }, - { - name: 'user_information', - type: 'keyword', - description: 'General user information. On Linux, this is the gecos field.\n', - }, - { - name: 'group', - type: 'object', - description: - "`group` contains information about any groups the user is part of (beyond the user's primary group).\n", - fields: [ - { - name: 'name', - type: 'keyword', - description: 'Group name.\n', - }, - { - name: 'gid', - type: 'integer', - description: 'Group ID.\n', - }, - ], - }, - { - name: 'password', - type: 'group', - description: - "`password` contains information about a user's password (not the password itself).\n", - fields: [ - { - name: 'type', - type: 'keyword', - description: - "A user's password type. Possible values are `shadow_password` (the password hash is in the shadow file), `password_disabled`, `no_password` (this is dangerous as anyone can log in), and `crypt_password` (when the password field in /etc/passwd seems to contain an encrypted password).\n", - }, - { - name: 'last_changed', - type: 'date', - description: "The day the user's password was last changed.\n", - }, - ], - }, - ], - }, - ], - }, - ], - }, -]; diff --git a/x-pack/plugins/security_solution/server/utils/beat_schema/8.0.0/ecs.ts b/x-pack/plugins/security_solution/server/utils/beat_schema/8.0.0/ecs.ts deleted file mode 100644 index a439d105d63df..0000000000000 --- a/x-pack/plugins/security_solution/server/utils/beat_schema/8.0.0/ecs.ts +++ /dev/null @@ -1,5675 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -/** - * An instance of the unmodified schema exported from https://github.com/elastic/ecs - * A map of `EcsNamespace.name` `->` `EcsNamespace` - * - * - NOTE: This instance does NOT include "virtual (non-spec)" ECS fields e.g `_id`. - * - NOTE: This instance does NOT include "mappings" from ECS fields, to `ECS` - * instances e.g. `@timestamp` to `timestamp` - */ - -import { Schema } from '../type'; - -export const ecsSchema: Schema = [ - { - key: 'ecs', - title: 'ECS', - description: 'ECS Fields.', - fields: [ - { - name: '@timestamp', - level: 'core', - required: true, - type: 'date', - description: - 'Date/time when the event originated.\n\nThis is the date/time extracted from the event, typically representing when\nthe event was generated by the source.\n\nIf the event source has no original timestamp, this value is typically populated\nby the first time the event was received by the pipeline.\n\nRequired field for all events.', - example: '2016-05-23T08:05:34.853Z', - }, - { - name: 'labels', - level: 'core', - type: 'object', - object_type: 'keyword', - description: - 'Custom key/value pairs.\n\nCan be used to add meta information to events. Should not contain nested objects.\nAll values are stored as keyword.\n\nExample: `docker` and `k8s` labels.', - example: '{"application": "foo-bar", "env": "production"}', - }, - { - name: 'message', - level: 'core', - type: 'text', - description: - 'For log events the message field contains the log message, optimized\nfor viewing in a log viewer.\n\nFor structured logs without an original message field, other fields can be concatenated\nto form a human-readable summary of the event.\n\nIf multiple messages exist, they can be combined into one message.', - example: 'Hello World', - }, - { - name: 'tags', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'List of keywords used to tag each event.', - example: '["production", "env2"]', - }, - { - name: 'agent', - title: 'Agent', - group: 2, - description: - 'The agent fields contain the data about the software entity, if\nany, that collects, detects, or observes events on a host, or takes measurements\non a host.\n\nExamples include Beats. Agents may also run on observers. ECS agent.* fields\nshall be populated with details of the agent running on the host or observer\nwhere the event happened or the measurement was taken.', - footnote: - 'Examples: In the case of Beats for logs, the agent.name is filebeat.\nFor APM, it is the agent running in the app/service. The agent information does\nnot change if data is sent through queuing systems like Kafka, Redis, or processing\nsystems such as Logstash or APM Server.', - type: 'group', - fields: [ - { - name: 'build.original', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Extended build information for the agent.\n\nThis field is intended to contain any build information that a data source\nmay provide, no specific formatting is required.', - example: - 'metricbeat version 7.6.0 (amd64), libbeat 7.6.0 [6a23e8f8f30f5001ba344e4e54d8d9cb82cb107c\nbuilt 2020-02-05 23:10:10 +0000 UTC]', - default_field: false, - }, - { - name: 'ephemeral_id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Ephemeral identifier of this agent (if one exists).\n\nThis id normally changes across restarts, but `agent.id` does not.', - example: '8a4f500f', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique identifier of this agent (if one exists).\n\nExample: For Beats this would be beat.id.', - example: '8a4f500d', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Custom name of the agent.\n\nThis is a name that can be given to an agent. This can be helpful if for example\ntwo Filebeat instances are running on the same host but a human readable separation\nis needed on which Filebeat instance data is coming from.\n\nIf no name is given, the name is often left empty.', - example: 'foo', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Type of the agent.\n\nThe agent type stays always the same and should be given by the agent used.\nIn case of Filebeat the agent would always be Filebeat also if two Filebeat\ninstances are run on the same machine.', - example: 'filebeat', - }, - { - name: 'version', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Version of the agent.', - example: '6.0.0-rc2', - }, - ], - }, - { - name: 'as', - title: 'Autonomous System', - group: 2, - description: - 'An autonomous system (AS) is a collection of connected Internet Protocol\n(IP) routing prefixes under the control of one or more network operators on\nbehalf of a single administrative entity or domain that presents a common, clearly\ndefined routing policy to the internet.', - type: 'group', - fields: [ - { - name: 'number', - level: 'extended', - type: 'long', - description: - 'Unique number allocated to the autonomous system. The autonomous\nsystem number (ASN) uniquely identifies each network on the Internet.', - example: 15169, - }, - { - name: 'organization.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Organization name.', - example: 'Google LLC', - }, - ], - }, - { - name: 'client', - title: 'Client', - group: 2, - description: - 'A client is defined as the initiator of a network connection for\nevents regarding sessions, connections, or bidirectional flow records.\n\nFor TCP events, the client is the initiator of the TCP connection that sends\nthe SYN packet(s). For other protocols, the client is generally the initiator\nor requestor in the network transaction. Some systems use the term "originator"\nto refer the client in TCP connections. The client fields describe details about\nthe system acting as the client in the network event. Client fields are usually\npopulated in conjunction with server fields. Client fields are generally not\npopulated for packet-level events.\n\nClient / server representations can add semantic context to an exchange, which\nis helpful to visualize the data in certain situations. If your context falls\nin that category, you should still ensure that source and destination are filled\nappropriately.', - type: 'group', - fields: [ - { - name: 'address', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Some event client addresses are defined ambiguously. The event\nwill sometimes list an IP, a domain or a unix socket. You should always store\nthe raw address in the `.address` field.\n\nThen it should be duplicated to `.ip` or `.domain`, depending on which one\nit is.', - }, - { - name: 'as.number', - level: 'extended', - type: 'long', - description: - 'Unique number allocated to the autonomous system. The autonomous\nsystem number (ASN) uniquely identifies each network on the Internet.', - example: 15169, - }, - { - name: 'as.organization.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Organization name.', - example: 'Google LLC', - }, - { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - description: 'Bytes sent from the client to the server.', - example: 184, - }, - { - name: 'domain', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Client domain.', - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: 'IP address of the client (IPv4 or IPv6).', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'MAC address of the client.', - }, - { - name: 'nat.ip', - level: 'extended', - type: 'ip', - description: - 'Translated IP of source based NAT sessions (e.g. internal client\nto internet).\n\nTypically connections traversing load balancers, firewalls, or routers.', - }, - { - name: 'nat.port', - level: 'extended', - type: 'long', - format: 'string', - description: - 'Translated port of source based NAT sessions (e.g. internal client\nto internet).\n\nTypically connections traversing load balancers, firewalls, or routers.', - }, - { - name: 'packets', - level: 'core', - type: 'long', - description: 'Packets sent from the client to the server.', - example: 12, - }, - { - name: 'port', - level: 'core', - type: 'long', - format: 'string', - description: 'Port of the client.', - }, - { - name: 'registered_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The highest registered client domain, stripped of the subdomain.\n\nFor example, the registered domain for "foo.google.com" is "google.com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last two labels will not work well for TLDs such as "co.uk".', - example: 'google.com', - }, - { - name: 'top_level_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The effective top level domain (eTLD), also known as the domain\nsuffix, is the last part of the domain name. For example, the top level domain\nfor google.com is "com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last label will not work well for effective TLDs such as "co.uk".', - example: 'co.uk', - }, - { - name: 'user.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the user is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.email', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'User email address.', - }, - { - name: 'user.full_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'user.group.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.group.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'user.group.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - { - name: 'user.hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'user.id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier of the user.', - }, - { - name: 'user.name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'cloud', - title: 'Cloud', - group: 2, - description: 'Fields related to the cloud or infrastructure the events are coming\nfrom.', - footnote: - 'Examples: If Metricbeat is running on an EC2 host and fetches data\nfrom its host, the cloud info contains the data about this machine. If Metricbeat\nruns on a remote machine outside the cloud and fetches data from a service running\nin the cloud, the field contains cloud data from the machine the service is\nrunning on.', - type: 'group', - fields: [ - { - name: 'account.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The cloud account or organization id used to identify different\nentities in a multi-tenant environment.\n\nExamples: AWS account id, Google Cloud ORG Id, or other unique identifier.', - example: 666777888999, - }, - { - name: 'availability_zone', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Availability zone in which this host is running.', - example: 'us-east-1c', - }, - { - name: 'instance.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Instance ID of the host machine.', - example: 'i-1234567890abcdef0', - }, - { - name: 'instance.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Instance name of the host machine.', - }, - { - name: 'machine.type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Machine type of the host machine.', - example: 't2.medium', - }, - { - name: 'provider', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the cloud provider. Example values are aws, azure, gcp,\nor digitalocean.', - example: 'aws', - }, - { - name: 'region', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Region in which this host is running.', - example: 'us-east-1', - }, - ], - }, - { - name: 'code_signature', - title: 'Code Signature', - group: 2, - description: 'These fields contain information about binary code signatures.', - type: 'group', - fields: [ - { - name: 'exists', - level: 'core', - type: 'boolean', - description: 'Boolean to capture if a signature is present.', - example: 'true', - default_field: false, - }, - { - name: 'status', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Additional information about the certificate status.\n\nThis is useful for logging cryptographic errors with the certificate validity\nor trust status. Leave unpopulated if the validity or trust of the certificate\nwas unchecked.', - example: 'ERROR_UNTRUSTED_ROOT', - default_field: false, - }, - { - name: 'subject_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Subject name of the code signer', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'trusted', - level: 'extended', - type: 'boolean', - description: - 'Stores the trust status of the certificate chain.\n\nValidating the trust of the certificate chain may be complicated, and this\nfield should only be populated by tools that actively check the status.', - example: 'true', - default_field: false, - }, - { - name: 'valid', - level: 'extended', - type: 'boolean', - description: - 'Boolean to capture if the digital signature is verified against\nthe binary content.\n\nLeave unpopulated if a certificate was unchecked.', - example: 'true', - default_field: false, - }, - ], - }, - { - name: 'container', - title: 'Container', - group: 2, - description: - 'Container fields are used for meta information about the specific\ncontainer that is the source of information.\n\nThese fields help correlate data based containers from any runtime.', - type: 'group', - fields: [ - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique container id.', - }, - { - name: 'image.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the image the container was built on.', - }, - { - name: 'image.tag', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Container image tags.', - }, - { - name: 'labels', - level: 'extended', - type: 'object', - object_type: 'keyword', - description: 'Image labels.', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Container name.', - }, - { - name: 'runtime', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Runtime managing this container.', - example: 'docker', - }, - ], - }, - { - name: 'destination', - title: 'Destination', - group: 2, - description: - 'Destination fields describe details about the destination of a packet/event.\n\nDestination fields are usually populated in conjunction with source fields.', - type: 'group', - fields: [ - { - name: 'address', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Some event destination addresses are defined ambiguously. The\nevent will sometimes list an IP, a domain or a unix socket. You should always\nstore the raw address in the `.address` field.\n\nThen it should be duplicated to `.ip` or `.domain`, depending on which one\nit is.', - }, - { - name: 'as.number', - level: 'extended', - type: 'long', - description: - 'Unique number allocated to the autonomous system. The autonomous\nsystem number (ASN) uniquely identifies each network on the Internet.', - example: 15169, - }, - { - name: 'as.organization.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Organization name.', - example: 'Google LLC', - }, - { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - description: 'Bytes sent from the destination to the source.', - example: 184, - }, - { - name: 'domain', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Destination domain.', - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: 'IP address of the destination (IPv4 or IPv6).', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'MAC address of the destination.', - }, - { - name: 'nat.ip', - level: 'extended', - type: 'ip', - description: - 'Translated ip of destination based NAT sessions (e.g. internet\nto private DMZ)\n\nTypically used with load balancers, firewalls, or routers.', - }, - { - name: 'nat.port', - level: 'extended', - type: 'long', - format: 'string', - description: - 'Port the source session is translated to by NAT Device.\n\nTypically used with load balancers, firewalls, or routers.', - }, - { - name: 'packets', - level: 'core', - type: 'long', - description: 'Packets sent from the destination to the source.', - example: 12, - }, - { - name: 'port', - level: 'core', - type: 'long', - format: 'string', - description: 'Port of the destination.', - }, - { - name: 'registered_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The highest registered destination domain, stripped of the subdomain.\n\nFor example, the registered domain for "foo.google.com" is "google.com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last two labels will not work well for TLDs such as "co.uk".', - example: 'google.com', - }, - { - name: 'top_level_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The effective top level domain (eTLD), also known as the domain\nsuffix, is the last part of the domain name. For example, the top level domain\nfor google.com is "com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last label will not work well for effective TLDs such as "co.uk".', - example: 'co.uk', - }, - { - name: 'user.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the user is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.email', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'User email address.', - }, - { - name: 'user.full_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'user.group.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.group.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'user.group.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - { - name: 'user.hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'user.id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier of the user.', - }, - { - name: 'user.name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'dll', - title: 'DLL', - group: 2, - description: - 'These fields contain information about code libraries dynamically\nloaded into processes.\n\n\nMany operating systems refer to "shared code libraries" with different names,\nbut this field set refers to all of the following:\n\n* Dynamic-link library (`.dll`) commonly used on Windows\n\n* Shared Object (`.so`) commonly used on Unix-like operating systems\n\n* Dynamic library (`.dylib`) commonly used on macOS', - type: 'group', - fields: [ - { - name: 'code_signature.exists', - level: 'core', - type: 'boolean', - description: 'Boolean to capture if a signature is present.', - example: 'true', - default_field: false, - }, - { - name: 'code_signature.status', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Additional information about the certificate status.\n\nThis is useful for logging cryptographic errors with the certificate validity\nor trust status. Leave unpopulated if the validity or trust of the certificate\nwas unchecked.', - example: 'ERROR_UNTRUSTED_ROOT', - default_field: false, - }, - { - name: 'code_signature.subject_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Subject name of the code signer', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'code_signature.trusted', - level: 'extended', - type: 'boolean', - description: - 'Stores the trust status of the certificate chain.\n\nValidating the trust of the certificate chain may be complicated, and this\nfield should only be populated by tools that actively check the status.', - example: 'true', - default_field: false, - }, - { - name: 'code_signature.valid', - level: 'extended', - type: 'boolean', - description: - 'Boolean to capture if the digital signature is verified against\nthe binary content.\n\nLeave unpopulated if a certificate was unchecked.', - example: 'true', - default_field: false, - }, - { - name: 'hash.md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'MD5 hash.', - default_field: false, - }, - { - name: 'hash.sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA1 hash.', - default_field: false, - }, - { - name: 'hash.sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA256 hash.', - default_field: false, - }, - { - name: 'hash.sha512', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA512 hash.', - default_field: false, - }, - { - name: 'name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the library.\n\nThis generally maps to the name of the file on disk.', - example: 'kernel32.dll', - default_field: false, - }, - { - name: 'path', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Full file path of the library.', - example: 'C:\\Windows\\System32\\kernel32.dll', - default_field: false, - }, - { - name: 'pe.architecture', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'CPU architecture target for the file.', - example: 'x64', - default_field: false, - }, - { - name: 'pe.company', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal company name of the file, provided at compile-time.', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'pe.description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal description of the file, provided at compile-time.', - example: 'Paint', - default_field: false, - }, - { - name: 'pe.file_version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal version of the file, provided at compile-time.', - example: '6.3.9600.17415', - default_field: false, - }, - { - name: 'pe.imphash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A hash of the imports in a PE file. An imphash -- or import hash\n-- can be used to fingerprint binaries even after recompilation or other code-level\ntransformations have occurred, which would change more traditional hash values.\n\nLearn more at https://www.fireeye.com/blog/threat-research/2014/01/tracking-malware-import-hashing.html.', - example: '0c6803c4e922103c4dca5963aad36ddf', - default_field: false, - }, - { - name: 'pe.original_file_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal name of the file, provided at compile-time.', - example: 'MSPAINT.EXE', - default_field: false, - }, - { - name: 'pe.product', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal product name of the file, provided at compile-time.', - example: 'Microsoft® Windows® Operating System', - default_field: false, - }, - ], - }, - { - name: 'dns', - title: 'DNS', - group: 2, - description: - 'Fields describing DNS queries and answers.\n\nDNS events should either represent a single DNS query prior to getting answers\n(`dns.type:query`) or they should represent a full exchange and contain the\nquery details as well as all of the answers that were provided for this query\n(`dns.type:answer`).', - type: 'group', - fields: [ - { - name: 'answers', - level: 'extended', - type: 'object', - object_type: 'keyword', - description: - 'An array containing an object for each answer section returned\nby the server.\n\nThe main keys that should be present in these objects are defined by ECS.\nRecords that have more information may contain more keys than what ECS defines.\n\nNot all DNS data sources give all details about DNS answers. At minimum, answer\nobjects must contain the `data` key. If more information is available, map\nas much of it to ECS as possible, and add any additional fields to the answer\nobjects as custom fields.', - }, - { - name: 'answers.class', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The class of DNS data contained in this resource record.', - example: 'IN', - }, - { - name: 'answers.data', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The data describing the resource.\n\nThe meaning of this data depends on the type and class of the resource record.', - example: '10.10.10.10', - }, - { - name: 'answers.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The domain name to which this resource record pertains.\n\nIf a chain of CNAME is being resolved, each answer `name` should be the\none that corresponds with the answer `data`. It should not simply be the\noriginal `question.name` repeated.', - example: 'www.google.com', - }, - { - name: 'answers.ttl', - level: 'extended', - type: 'long', - description: - 'The time interval in seconds that this resource record may be cached\nbefore it should be discarded. Zero values mean that the data should not be\ncached.', - example: 180, - }, - { - name: 'answers.type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The type of data contained in this resource record.', - example: 'CNAME', - }, - { - name: 'header_flags', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Array of 2 letter DNS header flags.\n\nExpected values are: AA, TC, RD, RA, AD, CD, DO.', - example: ['RD', 'RA'], - }, - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The DNS packet identifier assigned by the program that generated\nthe query. The identifier is copied to the response.', - example: 62111, - }, - { - name: 'op_code', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The DNS operation code that specifies the kind of query in the\nmessage. This value is set by the originator of a query and copied into the\nresponse.', - example: 'QUERY', - }, - { - name: 'question.class', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The class of records being queried.', - example: 'IN', - }, - { - name: 'question.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The name being queried.\n\nIf the name field contains non-printable characters (below 32 or above 126),\nthose characters should be represented as escaped base 10 integers (\\DDD).\nBack slashes and quotes should be escaped. Tabs, carriage returns, and line\nfeeds should be converted to \\t, \\r, and \\n respectively.', - example: 'www.google.com', - }, - { - name: 'question.registered_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The highest registered domain, stripped of the subdomain.\n\nFor example, the registered domain for "foo.google.com" is "google.com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last two labels will not work well for TLDs such as "co.uk".', - example: 'google.com', - }, - { - name: 'question.subdomain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The subdomain is all of the labels under the registered_domain.\n\nIf the domain has multiple levels of subdomain, such as "sub2.sub1.example.com",\nthe subdomain field should contain "sub2.sub1", with no trailing period.', - example: 'www', - }, - { - name: 'question.top_level_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The effective top level domain (eTLD), also known as the domain\nsuffix, is the last part of the domain name. For example, the top level domain\nfor google.com is "com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last label will not work well for effective TLDs such as "co.uk".', - example: 'co.uk', - }, - { - name: 'question.type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The type of record being queried.', - example: 'AAAA', - }, - { - name: 'resolved_ip', - level: 'extended', - type: 'ip', - description: - 'Array containing all IPs seen in `answers.data`.\n\nThe `answers` array can be difficult to use, because of the variety of data\nformats it can contain. Extracting all IP addresses seen in there to `dns.resolved_ip`\nmakes it possible to index them as IP addresses, and makes them easier to\nvisualize and query for.', - example: ['10.10.10.10', '10.10.10.11'], - }, - { - name: 'response_code', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The DNS response code.', - example: 'NOERROR', - }, - { - name: 'type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The type of DNS event captured, query or answer.\n\nIf your source of DNS events only gives you DNS queries, you should only create\ndns events of type `dns.type:query`.\n\nIf your source of DNS events gives you answers as well, you should create\none event per query (optionally as soon as the query is seen). And a second\nevent containing all query details as well as an array of answers.', - example: 'answer', - }, - ], - }, - { - name: 'ecs', - title: 'ECS', - group: 2, - description: 'Meta-information specific to ECS.', - type: 'group', - fields: [ - { - name: 'version', - level: 'core', - required: true, - type: 'keyword', - ignore_above: 1024, - description: - 'ECS version this event conforms to. `ecs.version` is a required\nfield and must exist in all events.\n\nWhen querying across multiple indices -- which may conform to slightly different\nECS versions -- this field lets integrations adjust to the schema version\nof the events.', - example: '1.0.0', - }, - ], - }, - { - name: 'error', - title: 'Error', - group: 2, - description: - 'These fields can represent errors of any kind.\n\nUse them for errors that happen while fetching events or in cases where the\nevent itself contains an error.', - type: 'group', - fields: [ - { - name: 'code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Error code describing the error.', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the error.', - }, - { - name: 'message', - level: 'core', - type: 'text', - description: 'Error message.', - }, - { - name: 'stack_trace', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'The stack trace of this error in plain text.', - }, - { - name: 'type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The type of the error, for example the class name of the exception.', - example: 'java.lang.NullPointerException', - }, - ], - }, - { - name: 'event', - title: 'Event', - group: 2, - description: - 'The event fields are used for context information about the log\nor metric event itself.\n\nA log is defined as an event containing details of something that happened.\nLog events must include the time at which the thing happened. Examples of log\nevents include a process starting on a host, a network packet being sent from\na source to a destination, or a network connection between a client and a server\nbeing initiated or closed. A metric is defined as an event containing one or\nmore numerical measurements and the time at which the measurement was taken.\nExamples of metric events include memory pressure measured on a host and device\ntemperature. See the `event.kind` definition in this section for additional\ndetails about metric and state events.', - type: 'group', - fields: [ - { - name: 'action', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'The action captured by the event.\n\nThis describes the information in the event. It is more specific than `event.category`.\nExamples are `group-add`, `process-started`, `file-created`. The value is\nnormally defined by the implementer.', - example: 'user-password-change', - }, - { - name: 'category', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'This is one of four ECS Categorization Fields, and indicates the\nsecond level in the ECS category hierarchy.\n\n`event.category` represents the "big buckets" of ECS categories. For example,\nfiltering on `event.category:process` yields all events relating to process\nactivity. This field is closely related to `event.type`, which is used as\na subcategory.\n\nThis field is an array. This will allow proper categorization of some events\nthat fall in multiple categories.', - example: 'authentication', - }, - { - name: 'code', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Identification code for this event, if one exists.\n\nSome event sources use event codes to identify messages unambiguously, regardless\nof message language or wording adjustments over time. An example of this is\nthe Windows Event ID.', - example: 4648, - }, - { - name: 'created', - level: 'core', - type: 'date', - description: - 'event.created contains the date/time when the event was first\nread by an agent, or by your pipeline.\n\nThis field is distinct from @timestamp in that @timestamp typically contain\nthe time extracted from the original event.\n\nIn most situations, these two timestamps will be slightly different. The difference\ncan be used to calculate the delay between your source generating an event,\nand the time when your agent first processed it. This can be used to monitor\nyour agent or pipeline ability to keep up with your event source.\n\nIn case the two timestamps are identical, @timestamp should be used.', - example: '2016-05-23T08:05:34.857Z', - }, - { - name: 'dataset', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the dataset.\n\nIf an event source publishes more than one type of log or events (e.g. access\nlog, error log), the dataset is used to specify which one the event comes\nfrom.\n\nIt is recommended but not required to start the dataset name with the module\nname, followed by a dot, then the dataset name.', - example: 'apache.access', - }, - { - name: 'duration', - level: 'core', - type: 'long', - format: 'duration', - input_format: 'nanoseconds', - output_format: 'asMilliseconds', - output_precision: 1, - description: - 'Duration of the event in nanoseconds.\n\nIf event.start and event.end are known this value should be the difference\nbetween the end and start time.', - }, - { - name: 'end', - level: 'extended', - type: 'date', - description: - 'event.end contains the date when the event ended or when the activity\nwas last observed.', - }, - { - name: 'hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Hash (perhaps logstash fingerprint) of raw field to be able to\ndemonstrate log integrity.', - example: '123456789012345678901234567890ABCD', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique ID to describe the event.', - example: '8a4f500d', - }, - { - name: 'ingested', - level: 'core', - type: 'date', - description: - 'Timestamp when an event arrived in the central data store.\n\nThis is different from `@timestamp`, which is when the event originally occurred. It is\nalso different from `event.created`, which is meant to capture the first time\nan agent saw the event.\n\nIn normal conditions, assuming no tampering, the timestamps should chronologically\nlook like this: `@timestamp` < `event.created` < `event.ingested`.', - example: '2016-05-23T08:05:35.101Z', - default_field: false, - }, - { - name: 'kind', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'This is one of four ECS Categorization Fields, and indicates the\nhighest level in the ECS category hierarchy.\n\n`event.kind` gives high-level information about what type of information the\nevent contains, without being specific to the contents of the event. For example,\nvalues of this field distinguish alert events from metric events.\n\nThe value of this field can be used to inform how these kinds of events should\nbe handled. They may warrant different retention, different access control,\nit may also help understand whether the data coming in at a regular interval\nor not.', - example: 'alert', - }, - { - name: 'module', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the module this data is coming from.\n\nIf your monitoring agent supports the concept of modules or plugins to process\nevents of a given source (e.g. Apache logs), `event.module` should contain\nthe name of this module.', - example: 'apache', - }, - { - name: 'original', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Raw text message of entire event. Used to demonstrate log integrity.\n\nThis field is not indexed and doc_values are disabled. It cannot be searched,\nbut it can be retrieved from `_source`.', - example: - 'Sep 19 08:26:10 host CEF:0|Security| threatmanager|1.0|100|\nworm successfully stopped|10|src=10.0.0.1 dst=2.1.2.2spt=1232', - }, - { - name: 'outcome', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'This is one of four ECS Categorization Fields, and indicates the\nlowest level in the ECS category hierarchy.\n\n`event.outcome` simply denotes whether the event represents a success or a\nfailure from the perspective of the entity that produced the event.\n\nNote that when a single transaction is described in multiple events, each\nevent may populate different values of `event.outcome`, according to their\nperspective.\n\nAlso note that in the case of a compound event (a single event that contains\nmultiple logical events), this field should be populated with the value that\nbest captures the overall success or failure from the perspective of the event\nproducer.\n\nFurther note that not all events will have an associated outcome. For example,\nthis field is generally not populated for metric events, events with `event.type:info`,\nor any events for which an outcome does not make logical sense.', - example: 'success', - }, - { - name: 'provider', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Source of the event.\n\nEvent transports such as Syslog or the Windows Event Log typically mention\nthe source of an event. It can be the name of the software that generated\nthe event (e.g. Sysmon, httpd), or of a subsystem of the operating system\n(kernel, Microsoft-Windows-Security-Auditing).', - example: 'kernel', - }, - { - name: 'reference', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Reference URL linking to additional information about this event.\n\nThis URL links to a static definition of the this event. Alert events, indicated\nby `event.kind:alert`, are a common use case for this field.', - example: 'https://system.vendor.com/event/#0001234', - default_field: false, - }, - { - name: 'risk_score', - level: 'core', - type: 'float', - description: - "Risk score or priority of the event (e.g. security solutions).\nUse your system's original value here.", - }, - { - name: 'risk_score_norm', - level: 'extended', - type: 'float', - description: - 'Normalized risk score or priority of the event, on a scale of\n0 to 100.\n\nThis is mainly useful if you use more than one system that assigns risk scores,\nand you want to see a normalized value across all systems.', - }, - { - name: 'sequence', - level: 'extended', - type: 'long', - format: 'string', - description: - 'Sequence number of the event.\n\nThe sequence number is a value published by some event sources, to make the\nexact ordering of events unambiguous, regardless of the timestamp precision.', - }, - { - name: 'severity', - level: 'core', - type: 'long', - format: 'string', - description: - 'The numeric severity of the event according to your event source.\n\nWhat the different severity values mean can be different between sources and\nuse cases. It is up to the implementer to make sure severities are consistent\nacross events from the same source.\n\nThe Syslog severity belongs in `log.syslog.severity.code`. `event.severity`\nis meant to represent the severity according to the event source (e.g. firewall,\nIDS). If the event source does not publish its own severity, you may optionally\ncopy the `log.syslog.severity.code` to `event.severity`.', - example: 7, - }, - { - name: 'start', - level: 'extended', - type: 'date', - description: - 'event.start contains the date when the event started or when the\nactivity was first observed.', - }, - { - name: 'timezone', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'This field should be populated when the event timestamp does\nnot include timezone information already (e.g. default Syslog timestamps).\nIt is optional otherwise.\n\nAcceptable timezone formats are: a canonical ID (e.g. "Europe/Amsterdam"),\nabbreviated (e.g. "EST") or an HH:mm differential (e.g. "-05:00").', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'This is one of four ECS Categorization Fields, and indicates the\nthird level in the ECS category hierarchy.\n\n`event.type` represents a categorization "sub-bucket" that, when used along\nwith the `event.category` field values, enables filtering events down to a\nlevel appropriate for single visualization.\n\nThis field is an array. This will allow proper categorization of some events\nthat fall in multiple event types.', - }, - { - name: 'url', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'URL linking to an external system to continue investigation of\nthis event.\n\nThis URL links to another system where in-depth investigation of the specific\noccurence of this event can take place. Alert events, indicated by `event.kind:alert`,\nare a common use case for this field.', - example: 'https://mysystem.mydomain.com/alert/5271dedb-f5b0-4218-87f0-4ac4870a38fe', - default_field: false, - }, - ], - }, - { - name: 'file', - title: 'File', - group: 2, - description: - 'A file is defined as a set of information that has been created\non, or has existed on a filesystem.\n\nFile objects can be associated with host events, network events, and/or file\nevents (e.g., those produced by File Integrity Monitoring [FIM] products or\nservices). File fields provide details about the affected file associated with\nthe event or metric.', - type: 'group', - fields: [ - { - name: 'accessed', - level: 'extended', - type: 'date', - description: - 'Last time the file was accessed.\n\nNote that not all filesystems keep track of access time.', - }, - { - name: 'attributes', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Array of file attributes.\n\nAttributes names will vary by platform. Here is a non-exhaustive list of values\nthat are expected in this field: archive, compressed, directory, encrypted,\nexecute, hidden, read, readonly, system, write.', - example: '["readonly", "system"]', - default_field: false, - }, - { - name: 'code_signature.exists', - level: 'core', - type: 'boolean', - description: 'Boolean to capture if a signature is present.', - example: 'true', - default_field: false, - }, - { - name: 'code_signature.status', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Additional information about the certificate status.\n\nThis is useful for logging cryptographic errors with the certificate validity\nor trust status. Leave unpopulated if the validity or trust of the certificate\nwas unchecked.', - example: 'ERROR_UNTRUSTED_ROOT', - default_field: false, - }, - { - name: 'code_signature.subject_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Subject name of the code signer', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'code_signature.trusted', - level: 'extended', - type: 'boolean', - description: - 'Stores the trust status of the certificate chain.\n\nValidating the trust of the certificate chain may be complicated, and this\nfield should only be populated by tools that actively check the status.', - example: 'true', - default_field: false, - }, - { - name: 'code_signature.valid', - level: 'extended', - type: 'boolean', - description: - 'Boolean to capture if the digital signature is verified against\nthe binary content.\n\nLeave unpopulated if a certificate was unchecked.', - example: 'true', - default_field: false, - }, - { - name: 'created', - level: 'extended', - type: 'date', - description: - 'File creation time.\n\nNote that not all filesystems store the creation time.', - }, - { - name: 'ctime', - level: 'extended', - type: 'date', - description: - 'Last time the file attributes or metadata changed.\n\nNote that changes to the file content will update `mtime`. This implies `ctime`\nwill be adjusted at the same time, since `mtime` is an attribute of the file.', - }, - { - name: 'device', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Device that is the source of the file.', - example: 'sda', - }, - { - name: 'directory', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Directory where the file is located. It should include the drive\nletter, when appropriate.', - example: '/home/alice', - }, - { - name: 'drive_letter', - level: 'extended', - type: 'keyword', - ignore_above: 1, - description: - 'Drive letter where the file is located. This field is only relevant\non Windows.\n\nThe value should be uppercase, and not include the colon.', - example: 'C', - default_field: false, - }, - { - name: 'extension', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'File extension.', - example: 'png', - }, - { - name: 'gid', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Primary group ID (GID) of the file.', - example: '1001', - }, - { - name: 'group', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Primary group name of the file.', - example: 'alice', - }, - { - name: 'hash.md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'MD5 hash.', - }, - { - name: 'hash.sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA1 hash.', - }, - { - name: 'hash.sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA256 hash.', - }, - { - name: 'hash.sha512', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA512 hash.', - }, - { - name: 'inode', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Inode representing the file in the filesystem.', - example: '256383', - }, - { - name: 'mime_type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'MIME type should identify the format of the file or stream of bytes\nusing https://www.iana.org/assignments/media-types/media-types.xhtml[IANA\nofficial types], where possible. When more than one type is applicable, the\nmost specific type should be used.', - default_field: false, - }, - { - name: 'mode', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Mode of the file in octal representation.', - example: '0640', - }, - { - name: 'mtime', - level: 'extended', - type: 'date', - description: 'Last time the file content was modified.', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the file including the extension, without the directory.', - example: 'example.png', - }, - { - name: 'owner', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: "File owner's username.", - example: 'alice', - }, - { - name: 'path', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: - 'Full path to the file, including the file name. It should include\nthe drive letter, when appropriate.', - example: '/home/alice/example.png', - }, - { - name: 'pe.architecture', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'CPU architecture target for the file.', - example: 'x64', - default_field: false, - }, - { - name: 'pe.company', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal company name of the file, provided at compile-time.', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'pe.description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal description of the file, provided at compile-time.', - example: 'Paint', - default_field: false, - }, - { - name: 'pe.file_version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal version of the file, provided at compile-time.', - example: '6.3.9600.17415', - default_field: false, - }, - { - name: 'pe.imphash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A hash of the imports in a PE file. An imphash -- or import hash\n-- can be used to fingerprint binaries even after recompilation or other code-level\ntransformations have occurred, which would change more traditional hash values.\n\nLearn more at https://www.fireeye.com/blog/threat-research/2014/01/tracking-malware-import-hashing.html.', - example: '0c6803c4e922103c4dca5963aad36ddf', - default_field: false, - }, - { - name: 'pe.original_file_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal name of the file, provided at compile-time.', - example: 'MSPAINT.EXE', - default_field: false, - }, - { - name: 'pe.product', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal product name of the file, provided at compile-time.', - example: 'Microsoft® Windows® Operating System', - default_field: false, - }, - { - name: 'size', - level: 'extended', - type: 'long', - description: 'File size in bytes.\n\nOnly relevant when `file.type` is "file".', - example: 16384, - }, - { - name: 'target_path', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Target path for symlinks.', - }, - { - name: 'type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'File type (file, dir, or symlink).', - example: 'file', - }, - { - name: 'uid', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The user ID (UID) or security identifier (SID) of the file owner.', - example: '1001', - }, - ], - }, - { - name: 'geo', - title: 'Geo', - group: 2, - description: - 'Geo fields can carry data about a specific location related to an\nevent.\n\nThis geolocation information can be derived from techniques such as Geo IP,\nor be user-supplied.', - type: 'group', - fields: [ - { - name: 'city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - ], - }, - { - name: 'group', - title: 'Group', - group: 2, - description: - 'The group fields are meant to represent groups that are relevant\nto the event.', - type: 'group', - fields: [ - { - name: 'domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - ], - }, - { - name: 'hash', - title: 'Hash', - group: 2, - description: - 'The hash fields represent different hash algorithms and their values.\n\nField names for common hashes (e.g. MD5, SHA1) are predefined. Add fields for\nother hashes by lowercasing the hash algorithm name and using underscore separators\nas appropriate (snake case, e.g. sha3_512).', - type: 'group', - fields: [ - { - name: 'md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'MD5 hash.', - }, - { - name: 'sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA1 hash.', - }, - { - name: 'sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA256 hash.', - }, - { - name: 'sha512', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA512 hash.', - }, - ], - }, - { - name: 'host', - title: 'Host', - group: 2, - description: - 'A host is defined as a general computing instance.\n\nECS host.* fields should be populated with details about the host on which the\nevent happened, or from which the measurement was taken. Host types include\nhardware, virtual machines, Docker containers, and Kubernetes nodes.', - type: 'group', - fields: [ - { - name: 'architecture', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system architecture.', - example: 'x86_64', - }, - { - name: 'domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the domain of which the host is a member.\n\nFor example, on Windows this could be the host Active Directory domain\nor NetBIOS domain name. For Linux this could be the domain of the host\nLDAP provider.', - example: 'CONTOSO', - default_field: false, - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'hostname', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Hostname of the host.\n\nIt normally contains what the `hostname` command returns on the host machine.', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique host id.\n\nAs hostname is not always unique, use values that are meaningful in your environment.\n\nExample: The current usage of `beat.name`.', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: 'Host ip addresses.', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Host mac addresses.', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the host.\n\nIt can contain what `hostname` returns on Unix systems, the fully qualified\ndomain name, or a name specified by the user. The sender decides which value\nto use.', - }, - { - name: 'os.family', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'OS family (such as redhat, debian, freebsd, windows).', - example: 'debian', - }, - { - name: 'os.full', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, including the version or code name.', - example: 'Mac OS Mojave', - }, - { - name: 'os.kernel', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system kernel version as a raw string.', - example: '4.4.0-112-generic', - }, - { - name: 'os.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, without the version.', - example: 'Mac OS X', - }, - { - name: 'os.platform', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'os.version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system version as a raw string.', - example: '10.14.1', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Type of host.\n\nFor Cloud providers this can be the machine type like `t2.medium`. If vm,\nthis could be the container, for example, or other information meaningful\nin your environment.', - }, - { - name: 'uptime', - level: 'extended', - type: 'long', - description: 'Seconds the host has been up.', - example: 1325, - }, - { - name: 'user.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the user is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.email', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'User email address.', - }, - { - name: 'user.full_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'user.group.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.group.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'user.group.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - { - name: 'user.hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'user.id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier of the user.', - }, - { - name: 'user.name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'http', - title: 'HTTP', - group: 2, - description: - 'Fields related to HTTP activity. Use the `url` field set to store\nthe url of the request.', - type: 'group', - fields: [ - { - name: 'request.body.bytes', - level: 'extended', - type: 'long', - format: 'bytes', - description: 'Size in bytes of the request body.', - example: 887, - }, - { - name: 'request.body.content', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'The full HTTP request body.', - example: 'Hello world', - }, - { - name: 'request.bytes', - level: 'extended', - type: 'long', - format: 'bytes', - description: 'Total size in bytes of the request (body and headers).', - example: 1437, - }, - { - name: 'request.method', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'HTTP request method.\n\nThe field value must be normalized to lowercase for querying. See the documentation\nsection "Implementing ECS".', - example: 'get, post, put', - }, - { - name: 'request.referrer', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Referrer for this HTTP request.', - example: 'https://blog.example.com/', - }, - { - name: 'response.body.bytes', - level: 'extended', - type: 'long', - format: 'bytes', - description: 'Size in bytes of the response body.', - example: 887, - }, - { - name: 'response.body.content', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'The full HTTP response body.', - example: 'Hello world', - }, - { - name: 'response.bytes', - level: 'extended', - type: 'long', - format: 'bytes', - description: 'Total size in bytes of the response (body and headers).', - example: 1437, - }, - { - name: 'response.status_code', - level: 'extended', - type: 'long', - format: 'string', - description: 'HTTP response status code.', - example: 404, - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'HTTP version.', - example: 1.1, - }, - ], - }, - { - name: 'interface', - title: 'Interface', - group: 2, - description: - 'The interface fields are used to record ingress and egress interface\ninformation when reported by an observer (e.g. firewall, router, load balancer)\nin the context of the observer handling a network connection. In the case of\na single observer interface (e.g. network sensor on a span port) only the observer.ingress\ninformation should be populated.', - type: 'group', - fields: [ - { - name: 'alias', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Interface alias as reported by the system, typically used in firewall\nimplementations for e.g. inside, outside, or dmz logical interface naming.', - example: 'outside', - default_field: false, - }, - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Interface ID as reported by an observer (typically SNMP interface\nID).', - example: 10, - default_field: false, - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Interface name as reported by the system.', - example: 'eth0', - default_field: false, - }, - ], - }, - { - name: 'log', - title: 'Log', - group: 2, - description: - 'Details about the event logging mechanism or logging transport.\n\nThe log.* fields are typically populated with details about the logging mechanism\nused to create and/or transport the event. For example, syslog details belong\nunder `log.syslog.*`.\n\nThe details specific to your event source are typically not logged under `log.*`,\nbut rather in `event.*` or in other ECS fields.', - type: 'group', - fields: [ - { - name: 'file.path', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - "Full path to the log file this event came from, including the\nfile name. It should include the drive letter, when appropriate.\n\nIf the event wasn't read from a log file, do not populate this field.", - example: '/var/log/fun-times.log', - default_field: false, - }, - { - name: 'level', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Original log level of the log event.\n\nIf the source of the event provides a log level or textual severity, this\nis the one that goes in `log.level`. If your source does not specify one,\nyou may put your event transport severity here (e.g. Syslog severity).\n\nSome examples are `warn`, `err`, `i`, `informational`.', - example: 'error', - }, - { - name: 'logger', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'The name of the logger inside an application. This is usually the\nname of the class which initialized the logger, or can be a custom name.', - example: 'org.elasticsearch.bootstrap.Bootstrap', - }, - { - name: 'origin.file.line', - level: 'extended', - type: 'integer', - description: - 'The line number of the file containing the source code which originated\nthe log event.', - example: 42, - }, - { - name: 'origin.file.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The name of the file containing the source code which originated\nthe log event.\n\nNote that this field is not meant to capture the log file. The correct field\nto capture the log file is `log.file.path`.', - example: 'Bootstrap.java', - }, - { - name: 'origin.function', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The name of the function or method which originated the log event.', - example: 'init', - }, - { - name: 'original', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'This is the original log message and contains the full log message\nbefore splitting it up in multiple parts.\n\nIn contrast to the `message` field which can contain an extracted part of\nthe log message, this field contains the original, full log message. It can\nhave already some modifications applied like encoding or new lines removed\nto clean up the log message.\n\nThis field is not indexed and doc_values are disabled so it cannot be queried\nbut the value can be retrieved from `_source`.', - example: 'Sep 19 08:26:10 localhost My log', - }, - { - name: 'syslog', - level: 'extended', - type: 'object', - object_type: 'keyword', - description: - 'The Syslog metadata of the event, if the event was transmitted\nvia Syslog. Please see RFCs 5424 or 3164.', - }, - { - name: 'syslog.facility.code', - level: 'extended', - type: 'long', - format: 'string', - description: - 'The Syslog numeric facility of the log event, if available.\n\nAccording to RFCs 5424 and 3164, this value should be an integer between 0\nand 23.', - example: 23, - }, - { - name: 'syslog.facility.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The Syslog text-based facility of the log event, if available.', - example: 'local7', - }, - { - name: 'syslog.priority', - level: 'extended', - type: 'long', - format: 'string', - description: - 'Syslog numeric priority of the event, if available.\n\nAccording to RFCs 5424 and 3164, the priority is 8 * facility + severity.\nThis number is therefore expected to contain a value between 0 and 191.', - example: 135, - }, - { - name: 'syslog.severity.code', - level: 'extended', - type: 'long', - description: - 'The Syslog numeric severity of the log event, if available.\n\nIf the event source publishing via Syslog provides a different numeric severity\nvalue (e.g. firewall, IDS), your source numeric severity should go to `event.severity`.\nIf the event source does not specify a distinct severity, you can optionally\ncopy the Syslog severity to `event.severity`.', - example: 3, - }, - { - name: 'syslog.severity.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The Syslog numeric severity of the log event, if available.\n\nIf the event source publishing via Syslog provides a different severity value\n(e.g. firewall, IDS), your source text severity should go to `log.level`.\nIf the event source does not specify a distinct severity, you can optionally\ncopy the Syslog severity to `log.level`.', - example: 'Error', - }, - ], - }, - { - name: 'network', - title: 'Network', - group: 2, - description: - 'The network is defined as the communication path over which a host\nor network event happens.\n\nThe network.* fields should be populated with details about the network activity\nassociated with an event.', - type: 'group', - fields: [ - { - name: 'application', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A name given to an application level protocol. This can be arbitrarily\nassigned for things like microservices, but also apply to things like skype,\nicq, facebook, twitter. This would be used in situations where the vendor\nor service can be decoded such as from the source/dest IP owners, ports, or\nwire format.\n\nThe field value must be normalized to lowercase for querying. See the documentation\nsection "Implementing ECS".', - example: 'aim', - }, - { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - description: - 'Total bytes transferred in both directions.\n\nIf `source.bytes` and `destination.bytes` are known, `network.bytes` is their\nsum.', - example: 368, - }, - { - name: 'community_id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A hash of source and destination IPs and ports, as well as the\nprotocol used in a communication. This is a tool-agnostic standard to identify\nflows.\n\nLearn more at https://github.com/corelight/community-id-spec.', - example: '1:hO+sN4H+MG5MY/8hIrXPqc4ZQz0=', - }, - { - name: 'direction', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - "Direction of the network traffic.\nRecommended values are:\n * inbound\n * outbound\n * internal\n * external\n * unknown\n\nWhen mapping events from a host-based monitoring context, populate this field from the host's point of view.\nWhen mapping events from a network or perimeter-based monitoring context, populate this field from the point of view of your network perimeter.", - example: 'inbound', - }, - { - name: 'forwarded_ip', - level: 'core', - type: 'ip', - description: 'Host IP address when the source IP address is the proxy.', - example: '192.1.1.2', - }, - { - name: 'iana_number', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'IANA Protocol Number (https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml).\nStandardized list of protocols. This aligns well with NetFlow and sFlow related\nlogs which use the IANA Protocol Number.', - example: 6, - }, - { - name: 'inner', - level: 'extended', - type: 'object', - object_type: 'keyword', - description: - 'Network.inner fields are added in addition to network.vlan fields\nto describe the innermost VLAN when q-in-q VLAN tagging is present. Allowed\nfields include vlan.id and vlan.name. Inner vlan fields are typically used\nwhen sending traffic with multiple 802.1q encapsulations to a network sensor\n(e.g. Zeek, Wireshark.)', - default_field: false, - }, - { - name: 'inner.vlan.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'VLAN ID as reported by the observer.', - example: 10, - default_field: false, - }, - { - name: 'inner.vlan.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Optional VLAN name as reported by the observer.', - example: 'outside', - default_field: false, - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name given by operators to sections of their network.', - example: 'Guest Wifi', - }, - { - name: 'packets', - level: 'core', - type: 'long', - description: - 'Total packets transferred in both directions.\n\nIf `source.packets` and `destination.packets` are known, `network.packets`\nis their sum.', - example: 24, - }, - { - name: 'protocol', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'L7 Network protocol name. ex. http, lumberjack, transport protocol.\n\nThe field value must be normalized to lowercase for querying. See the documentation\nsection "Implementing ECS".', - example: 'http', - }, - { - name: 'transport', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Same as network.iana_number, but instead using the Keyword name\nof the transport layer (udp, tcp, ipv6-icmp, etc.)\n\nThe field value must be normalized to lowercase for querying. See the documentation\nsection "Implementing ECS".', - example: 'tcp', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'In the OSI Model this would be the Network Layer. ipv4, ipv6,\nipsec, pim, etc\n\nThe field value must be normalized to lowercase for querying. See the documentation\nsection "Implementing ECS".', - example: 'ipv4', - }, - { - name: 'vlan.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'VLAN ID as reported by the observer.', - example: 10, - default_field: false, - }, - { - name: 'vlan.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Optional VLAN name as reported by the observer.', - example: 'outside', - default_field: false, - }, - ], - }, - { - name: 'observer', - title: 'Observer', - group: 2, - description: - 'An observer is defined as a special network, security, or application\ndevice used to detect, observe, or create network, security, or application-related\nevents and metrics.\n\nThis could be a custom hardware appliance or a server that has been configured\nto run special network, security, or application software. Examples include\nfirewalls, web proxies, intrusion detection/prevention systems, network monitoring\nsensors, web application firewalls, data loss prevention systems, and APM servers.\nThe observer.* fields shall be populated with details of the system, if any,\nthat detects, observes and/or creates a network, security, or application event\nor metric. Message queues and ETL components used in processing events or metrics\nare not considered observers in ECS.', - type: 'group', - fields: [ - { - name: 'egress', - level: 'extended', - type: 'object', - object_type: 'keyword', - description: - 'Observer.egress holds information like interface number and name,\nvlan, and zone information to classify egress traffic. Single armed monitoring\nsuch as a network sensor on a span port should only use observer.ingress\nto categorize traffic.', - default_field: false, - }, - { - name: 'egress.interface.alias', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Interface alias as reported by the system, typically used in firewall\nimplementations for e.g. inside, outside, or dmz logical interface naming.', - example: 'outside', - default_field: false, - }, - { - name: 'egress.interface.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Interface ID as reported by an observer (typically SNMP interface\nID).', - example: 10, - default_field: false, - }, - { - name: 'egress.interface.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Interface name as reported by the system.', - example: 'eth0', - default_field: false, - }, - { - name: 'egress.vlan.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'VLAN ID as reported by the observer.', - example: 10, - default_field: false, - }, - { - name: 'egress.vlan.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Optional VLAN name as reported by the observer.', - example: 'outside', - default_field: false, - }, - { - name: 'egress.zone', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Network zone of outbound traffic as reported by the observer to\ncategorize the destination area of egress traffic, e.g. Internal, External,\nDMZ, HR, Legal, etc.', - example: 'Public_Internet', - default_field: false, - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'hostname', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Hostname of the observer.', - }, - { - name: 'ingress', - level: 'extended', - type: 'object', - object_type: 'keyword', - description: - 'Observer.ingress holds information like interface number and name,\nvlan, and zone information to classify ingress traffic. Single armed monitoring\nsuch as a network sensor on a span port should only use observer.ingress\nto categorize traffic.', - default_field: false, - }, - { - name: 'ingress.interface.alias', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Interface alias as reported by the system, typically used in firewall\nimplementations for e.g. inside, outside, or dmz logical interface naming.', - example: 'outside', - default_field: false, - }, - { - name: 'ingress.interface.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Interface ID as reported by an observer (typically SNMP interface\nID).', - example: 10, - default_field: false, - }, - { - name: 'ingress.interface.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Interface name as reported by the system.', - example: 'eth0', - default_field: false, - }, - { - name: 'ingress.vlan.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'VLAN ID as reported by the observer.', - example: 10, - default_field: false, - }, - { - name: 'ingress.vlan.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Optional VLAN name as reported by the observer.', - example: 'outside', - default_field: false, - }, - { - name: 'ingress.zone', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Network zone of incoming traffic as reported by the observer to\ncategorize the source area of ingress traffic. e.g. internal, External, DMZ,\nHR, Legal, etc.', - example: 'DMZ', - default_field: false, - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: 'IP addresses of the observer.', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'MAC addresses of the observer', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Custom name of the observer.\n\nThis is a name that can be given to an observer. This can be helpful for example\nif multiple firewalls of the same model are used in an organization.\n\nIf no custom name is needed, the field can be left empty.', - example: '1_proxySG', - }, - { - name: 'os.family', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'OS family (such as redhat, debian, freebsd, windows).', - example: 'debian', - }, - { - name: 'os.full', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, including the version or code name.', - example: 'Mac OS Mojave', - }, - { - name: 'os.kernel', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system kernel version as a raw string.', - example: '4.4.0-112-generic', - }, - { - name: 'os.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, without the version.', - example: 'Mac OS X', - }, - { - name: 'os.platform', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'os.version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system version as a raw string.', - example: '10.14.1', - }, - { - name: 'product', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The product name of the observer.', - example: 's200', - }, - { - name: 'serial_number', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Observer serial number.', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'The type of the observer the data is coming from.\n\nThere is no predefined list of observer types. Some examples are `forwarder`,\n`firewall`, `ids`, `ips`, `proxy`, `poller`, `sensor`, `APM server`.', - example: 'firewall', - }, - { - name: 'vendor', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Vendor name of the observer.', - example: 'Symantec', - }, - { - name: 'version', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Observer version.', - }, - ], - }, - { - name: 'organization', - title: 'Organization', - group: 2, - description: - 'The organization fields enrich data with information about the company\nor entity the data is associated with.\n\nThese fields help you arrange or filter data stored in an index by one or multiple\norganizations.', - type: 'group', - fields: [ - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the organization.', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Organization name.', - }, - ], - }, - { - name: 'os', - title: 'Operating System', - group: 2, - description: 'The OS fields contain information about the operating system.', - type: 'group', - fields: [ - { - name: 'family', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'OS family (such as redhat, debian, freebsd, windows).', - example: 'debian', - }, - { - name: 'full', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, including the version or code name.', - example: 'Mac OS Mojave', - }, - { - name: 'kernel', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system kernel version as a raw string.', - example: '4.4.0-112-generic', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, without the version.', - example: 'Mac OS X', - }, - { - name: 'platform', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system version as a raw string.', - example: '10.14.1', - }, - ], - }, - { - name: 'package', - title: 'Package', - group: 2, - description: - 'These fields contain information about an installed software package.\nIt contains general information about a package, such as name, version or size.\nIt also contains installation details, such as time or location.', - type: 'group', - fields: [ - { - name: 'architecture', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Package architecture.', - example: 'x86_64', - }, - { - name: 'build_version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Additional information about the build version of the installed\npackage.\n\nFor example use the commit SHA of a non-released package.', - example: '36f4f7e89dd61b0988b12ee000b98966867710cd', - default_field: false, - }, - { - name: 'checksum', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Checksum of the installed package for verification.', - example: '68b329da9893e34099c7d8ad5cb9c940', - }, - { - name: 'description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Description of the package.', - example: - 'Open source programming language to build simple/reliable/efficient\nsoftware.', - }, - { - name: 'install_scope', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Indicating how the package was installed, e.g. user-local, global.', - example: 'global', - }, - { - name: 'installed', - level: 'extended', - type: 'date', - description: 'Time when package was installed.', - }, - { - name: 'license', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'License under which the package was released.\n\nUse a short name, e.g. the license identifier from SPDX License List where\npossible (https://spdx.org/licenses/).', - example: 'Apache License 2.0', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Package name', - example: 'go', - }, - { - name: 'path', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Path where the package is installed.', - example: '/usr/local/Cellar/go/1.12.9/', - }, - { - name: 'reference', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Home page or reference URL of the software in this package, if\navailable.', - example: 'https://golang.org', - default_field: false, - }, - { - name: 'size', - level: 'extended', - type: 'long', - format: 'string', - description: 'Package size in bytes.', - example: 62231, - }, - { - name: 'type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Type of package.\n\nThis should contain the package file type, rather than the package manager\nname. Examples: rpm, dpkg, brew, npm, gem, nupkg, jar.', - example: 'rpm', - default_field: false, - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Package version', - example: '1.12.9', - }, - ], - }, - { - name: 'pe', - title: 'PE Header', - group: 2, - description: 'These fields contain Windows Portable Executable (PE) metadata.', - type: 'group', - fields: [ - { - name: 'architecture', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'CPU architecture target for the file.', - example: 'x64', - default_field: false, - }, - { - name: 'company', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal company name of the file, provided at compile-time.', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal description of the file, provided at compile-time.', - example: 'Paint', - default_field: false, - }, - { - name: 'file_version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal version of the file, provided at compile-time.', - example: '6.3.9600.17415', - default_field: false, - }, - { - name: 'imphash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A hash of the imports in a PE file. An imphash -- or import hash\n-- can be used to fingerprint binaries even after recompilation or other code-level\ntransformations have occurred, which would change more traditional hash values.\n\nLearn more at https://www.fireeye.com/blog/threat-research/2014/01/tracking-malware-import-hashing.html.', - example: '0c6803c4e922103c4dca5963aad36ddf', - default_field: false, - }, - { - name: 'original_file_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal name of the file, provided at compile-time.', - example: 'MSPAINT.EXE', - default_field: false, - }, - { - name: 'product', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal product name of the file, provided at compile-time.', - example: 'Microsoft® Windows® Operating System', - default_field: false, - }, - ], - }, - { - name: 'process', - title: 'Process', - group: 2, - description: - 'These fields contain information about a process.\n\nThese fields can help you correlate metrics information with a process id/name\nfrom a log message. The `process.pid` often stays in the metric itself and\nis copied to the global field for correlation.', - type: 'group', - fields: [ - { - name: 'args', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.', - example: ['/usr/bin/ssh', '-l', 'user', '10.0.0.16'], - }, - { - name: 'args_count', - level: 'extended', - type: 'long', - description: - 'Length of the process.args array.\n\nThis field can be useful for querying or performing bucket analysis on how\nmany arguments were provided to start a process. More arguments may be an\nindication of suspicious activity.', - example: 4, - default_field: false, - }, - { - name: 'code_signature.exists', - level: 'core', - type: 'boolean', - description: 'Boolean to capture if a signature is present.', - example: 'true', - default_field: false, - }, - { - name: 'code_signature.status', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Additional information about the certificate status.\n\nThis is useful for logging cryptographic errors with the certificate validity\nor trust status. Leave unpopulated if the validity or trust of the certificate\nwas unchecked.', - example: 'ERROR_UNTRUSTED_ROOT', - default_field: false, - }, - { - name: 'code_signature.subject_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Subject name of the code signer', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'code_signature.trusted', - level: 'extended', - type: 'boolean', - description: - 'Stores the trust status of the certificate chain.\n\nValidating the trust of the certificate chain may be complicated, and this\nfield should only be populated by tools that actively check the status.', - example: 'true', - default_field: false, - }, - { - name: 'code_signature.valid', - level: 'extended', - type: 'boolean', - description: - 'Boolean to capture if the digital signature is verified against\nthe binary content.\n\nLeave unpopulated if a certificate was unchecked.', - example: 'true', - default_field: false, - }, - { - name: 'command_line', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: - 'Full command line that started the process, including the absolute\npath to the executable, and all arguments.\n\nSome arguments may be filtered to protect sensitive information.', - example: '/usr/bin/ssh -l user 10.0.0.16', - default_field: false, - }, - { - name: 'entity_id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique identifier for the process.\n\nThe implementation of this is specified by the data source, but some examples\nof what could be used here are a process-generated UUID, Sysmon Process GUIDs,\nor a hash of some uniquely identifying components of a process.\n\nConstructing a globally unique identifier is a common practice to mitigate\nPID reuse as well as to identify a specific process over time, across multiple\nmonitored hosts.', - example: 'c2c455d9f99375d', - default_field: false, - }, - { - name: 'executable', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Absolute path to the process executable.', - example: '/usr/bin/ssh', - }, - { - name: 'exit_code', - level: 'extended', - type: 'long', - description: - 'The exit code of the process, if this is a termination event.\n\nThe field should be absent if there is no exit code for the event (e.g. process\nstart).', - example: 137, - default_field: false, - }, - { - name: 'hash.md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'MD5 hash.', - }, - { - name: 'hash.sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA1 hash.', - }, - { - name: 'hash.sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA256 hash.', - }, - { - name: 'hash.sha512', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA512 hash.', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Process name.\n\nSometimes called program name or similar.', - example: 'ssh', - }, - { - name: 'parent.args', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Array of process arguments.\n\nMay be filtered to protect sensitive information.', - example: ['ssh', '-l', 'user', '10.0.0.16'], - default_field: false, - }, - { - name: 'parent.args_count', - level: 'extended', - type: 'long', - description: - 'Length of the process.args array.\n\nThis field can be useful for querying or performing bucket analysis on how\nmany arguments were provided to start a process. More arguments may be an\nindication of suspicious activity.', - example: 4, - default_field: false, - }, - { - name: 'parent.code_signature.exists', - level: 'core', - type: 'boolean', - description: 'Boolean to capture if a signature is present.', - example: 'true', - default_field: false, - }, - { - name: 'parent.code_signature.status', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Additional information about the certificate status.\n\nThis is useful for logging cryptographic errors with the certificate validity\nor trust status. Leave unpopulated if the validity or trust of the certificate\nwas unchecked.', - example: 'ERROR_UNTRUSTED_ROOT', - default_field: false, - }, - { - name: 'parent.code_signature.subject_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Subject name of the code signer', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'parent.code_signature.trusted', - level: 'extended', - type: 'boolean', - description: - 'Stores the trust status of the certificate chain.\n\nValidating the trust of the certificate chain may be complicated, and this\nfield should only be populated by tools that actively check the status.', - example: 'true', - default_field: false, - }, - { - name: 'parent.code_signature.valid', - level: 'extended', - type: 'boolean', - description: - 'Boolean to capture if the digital signature is verified against\nthe binary content.\n\nLeave unpopulated if a certificate was unchecked.', - example: 'true', - default_field: false, - }, - { - name: 'parent.command_line', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: - 'Full command line that started the process, including the absolute\npath to the executable, and all arguments.\n\nSome arguments may be filtered to protect sensitive information.', - example: '/usr/bin/ssh -l user 10.0.0.16', - default_field: false, - }, - { - name: 'parent.entity_id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique identifier for the process.\n\nThe implementation of this is specified by the data source, but some examples\nof what could be used here are a process-generated UUID, Sysmon Process GUIDs,\nor a hash of some uniquely identifying components of a process.\n\nConstructing a globally unique identifier is a common practice to mitigate\nPID reuse as well as to identify a specific process over time, across multiple\nmonitored hosts.', - example: 'c2c455d9f99375d', - default_field: false, - }, - { - name: 'parent.executable', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: 'Absolute path to the process executable.', - example: '/usr/bin/ssh', - default_field: false, - }, - { - name: 'parent.exit_code', - level: 'extended', - type: 'long', - description: - 'The exit code of the process, if this is a termination event.\n\nThe field should be absent if there is no exit code for the event (e.g. process\nstart).', - example: 137, - default_field: false, - }, - { - name: 'parent.hash.md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'MD5 hash.', - default_field: false, - }, - { - name: 'parent.hash.sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA1 hash.', - default_field: false, - }, - { - name: 'parent.hash.sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA256 hash.', - default_field: false, - }, - { - name: 'parent.hash.sha512', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA512 hash.', - default_field: false, - }, - { - name: 'parent.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: 'Process name.\n\nSometimes called program name or similar.', - example: 'ssh', - default_field: false, - }, - { - name: 'parent.pgid', - level: 'extended', - type: 'long', - format: 'string', - description: 'Identifier of the group of processes the process belongs to.', - default_field: false, - }, - { - name: 'parent.pid', - level: 'core', - type: 'long', - format: 'string', - description: 'Process id.', - example: 4242, - default_field: false, - }, - { - name: 'parent.ppid', - level: 'extended', - type: 'long', - format: 'string', - description: "Parent process' pid.", - example: 4241, - default_field: false, - }, - { - name: 'parent.start', - level: 'extended', - type: 'date', - description: 'The time the process started.', - example: '2016-05-23T08:05:34.853Z', - default_field: false, - }, - { - name: 'parent.thread.id', - level: 'extended', - type: 'long', - format: 'string', - description: 'Thread ID.', - example: 4242, - default_field: false, - }, - { - name: 'parent.thread.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Thread name.', - example: 'thread-0', - default_field: false, - }, - { - name: 'parent.title', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: - 'Process title.\n\nThe proctitle, some times the same as process name. Can also be different:\nfor example a browser setting its title to the web page currently opened.', - default_field: false, - }, - { - name: 'parent.uptime', - level: 'extended', - type: 'long', - description: 'Seconds the process has been up.', - example: 1325, - default_field: false, - }, - { - name: 'parent.working_directory', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: 'The working directory of the process.', - example: '/home/alice', - default_field: false, - }, - { - name: 'pe.architecture', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'CPU architecture target for the file.', - example: 'x64', - default_field: false, - }, - { - name: 'pe.company', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal company name of the file, provided at compile-time.', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'pe.description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal description of the file, provided at compile-time.', - example: 'Paint', - default_field: false, - }, - { - name: 'pe.file_version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal version of the file, provided at compile-time.', - example: '6.3.9600.17415', - default_field: false, - }, - { - name: 'pe.imphash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A hash of the imports in a PE file. An imphash -- or import hash\n-- can be used to fingerprint binaries even after recompilation or other code-level\ntransformations have occurred, which would change more traditional hash values.\n\nLearn more at https://www.fireeye.com/blog/threat-research/2014/01/tracking-malware-import-hashing.html.', - example: '0c6803c4e922103c4dca5963aad36ddf', - default_field: false, - }, - { - name: 'pe.original_file_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal name of the file, provided at compile-time.', - example: 'MSPAINT.EXE', - default_field: false, - }, - { - name: 'pe.product', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal product name of the file, provided at compile-time.', - example: 'Microsoft® Windows® Operating System', - default_field: false, - }, - { - name: 'pgid', - level: 'extended', - type: 'long', - format: 'string', - description: 'Identifier of the group of processes the process belongs to.', - }, - { - name: 'pid', - level: 'core', - type: 'long', - format: 'string', - description: 'Process id.', - example: 4242, - }, - { - name: 'ppid', - level: 'extended', - type: 'long', - format: 'string', - description: "Parent process' pid.", - example: 4241, - }, - { - name: 'start', - level: 'extended', - type: 'date', - description: 'The time the process started.', - example: '2016-05-23T08:05:34.853Z', - }, - { - name: 'thread.id', - level: 'extended', - type: 'long', - format: 'string', - description: 'Thread ID.', - example: 4242, - }, - { - name: 'thread.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Thread name.', - example: 'thread-0', - }, - { - name: 'title', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: - 'Process title.\n\nThe proctitle, some times the same as process name. Can also be different:\nfor example a browser setting its title to the web page currently opened.', - }, - { - name: 'uptime', - level: 'extended', - type: 'long', - description: 'Seconds the process has been up.', - example: 1325, - }, - { - name: 'working_directory', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'The working directory of the process.', - example: '/home/alice', - }, - ], - }, - { - name: 'registry', - title: 'Registry', - group: 2, - description: 'Fields related to Windows Registry operations.', - type: 'group', - fields: [ - { - name: 'data.bytes', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Original bytes written with base64 encoding.\n\nFor Windows registry operations, such as SetValueEx and RegQueryValueEx, this\ncorresponds to the data pointed by `lp_data`. This is optional but provides\nbetter recoverability and should be populated for REG_BINARY encoded values.', - example: 'ZQBuAC0AVQBTAAAAZQBuAAAAAAA=', - default_field: false, - }, - { - name: 'data.strings', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Content when writing string types.\n\nPopulated as an array when writing string data to the registry. For single\nstring registry types (REG_SZ, REG_EXPAND_SZ), this should be an array with\none string. For sequences of string with REG_MULTI_SZ, this array will be\nvariable length. For numeric data, such as REG_DWORD and REG_QWORD, this should\nbe populated with the decimal representation (e.g `"1"`).', - example: '["C:\\rta\\red_ttp\\bin\\myapp.exe"]', - default_field: false, - }, - { - name: 'data.type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Standard registry type for encoding contents', - example: 'REG_SZ', - default_field: false, - }, - { - name: 'hive', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Abbreviated name for the hive.', - example: 'HKLM', - default_field: false, - }, - { - name: 'key', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Hive-relative path of keys.', - example: - 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\winword.exe', - default_field: false, - }, - { - name: 'path', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Full path, including hive, key and value', - example: - 'HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution\nOptions\\winword.exe\\Debugger', - default_field: false, - }, - { - name: 'value', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the value written.', - example: 'Debugger', - default_field: false, - }, - ], - }, - { - name: 'related', - title: 'Related', - group: 2, - description: - 'This field set is meant to facilitate pivoting around a piece of\ndata.\n\nSome pieces of information can be seen in many places in an ECS event. To facilitate\nsearching for them, store an array of all seen values to their corresponding\nfield in `related.`.\n\nA concrete example is IP addresses, which can be under host, observer, source,\ndestination, client, server, and network.forwarded_ip. If you append all IPs\nto `related.ip`, you can then search for a given IP trivially, no matter where\nit appeared, by querying `related.ip:192.0.2.15`.', - type: 'group', - fields: [ - { - name: 'hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - "All the hashes seen on your event. Populating this field, then\nusing it to search for hashes can help in situations where you're unsure what\nthe hash algorithm is (and therefore which key name to search).", - default_field: false, - }, - { - name: 'ip', - level: 'extended', - type: 'ip', - description: 'All of the IPs seen on your event.', - }, - { - name: 'user', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'All the user names seen on your event.', - default_field: false, - }, - ], - }, - { - name: 'rule', - title: 'Rule', - group: 2, - description: - 'Rule fields are used to capture the specifics of any observer or\nagent rules that generate alerts or other notable events.\n\nExamples of data sources that would populate the rule fields include: network\nadmission control platforms, network or host IDS/IPS, network firewalls, web\napplication firewalls, url filters, endpoint detection and response (EDR) systems,\netc.', - type: 'group', - fields: [ - { - name: 'author', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name, organization, or pseudonym of the author or authors who created\nthe rule used to generate this event.', - example: ['Star-Lord'], - default_field: false, - }, - { - name: 'category', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A categorization value keyword used by the entity using the rule\nfor detection of this event.', - example: 'Attempted Information Leak', - default_field: false, - }, - { - name: 'description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The description of the rule generating the event.', - example: 'Block requests to public DNS over HTTPS / TLS protocols', - default_field: false, - }, - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A rule ID that is unique within the scope of an agent, observer,\nor other entity using the rule for detection of this event.', - example: 101, - default_field: false, - }, - { - name: 'license', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the license under which the rule used to generate this\nevent is made available.', - example: 'Apache 2.0', - default_field: false, - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The name of the rule or signature generating the event.', - example: 'BLOCK_DNS_over_TLS', - default_field: false, - }, - { - name: 'reference', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Reference URL to additional information about the rule used to\ngenerate this event.\n\nThe URL can point to the vendor documentation about the rule. If that is\nnot available, it can also be a link to a more general page describing this\ntype of alert.', - example: 'https://en.wikipedia.org/wiki/DNS_over_TLS', - default_field: false, - }, - { - name: 'ruleset', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the ruleset, policy, group, or parent category in which\nthe rule used to generate this event is a member.', - example: 'Standard_Protocol_Filters', - default_field: false, - }, - { - name: 'uuid', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A rule ID that is unique within the scope of a set or group of\nagents, observers, or other entities using the rule for detection of this\nevent.', - example: 1100110011, - default_field: false, - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The version / revision of the rule being used for analysis.', - example: 1.1, - default_field: false, - }, - ], - }, - { - name: 'search', - title: 'Search', - group: 2, - description: - 'The Search fields describe information about a search request event:\nquery or pagination. The fields that should be used with this field set include:\n`event.action` to describe the search action (e.g. `search.query`, `search.page`,\netc.), `event.duration` to describe the duration of a search request, `@timestamp`\nto record the event original timestamp and optionally the `source` fields\nto record context information such as `user.id` or `geo`.', - type: 'group', - fields: [ - { - name: 'query.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'An opaque query identifier. This identifier needs to be unique\nto a user query, and all subsequent events (pagination, clicks) need to have\nthe same query identifier.', - example: '2dc15175-de0d-44db-86d8-8a99f41b7a11', - default_field: false, - }, - { - name: 'query.page', - level: 'extended', - type: 'long', - description: - 'For search results that support pagination, this represents the\ncurrent page being requested. Initial search requests are `1` while subsequent\npage requests are incremental.', - example: 1, - default_field: false, - }, - { - name: 'query.value', - level: 'extended', - type: 'keyword', - ignore_above: 4096, - description: - 'The query string being searched on. This field is not analyzed\nand should not be pre-processed in any way in the event (e.g. normalization\nlist lowercasing). This is useful for search use-cases that use a one- box\nstyle search interface. Other interfaces will have to rely on additional custom\nfields or labels to represent things like filters applied, extra parameters,\nuser context, etc.', - example: 'where does the rain in Spain mainly fall', - default_field: false, - }, - { - name: 'results.ids', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - "A list of opaque document IDs representing the results that were\nshown to the user. This is effectively the impression list and it's size should\nbe equal to `results.size`. This field can be empty when there are no results\nto return.", - example: ['user:82375akja9f', 'issue:2782630'], - default_field: false, - }, - { - name: 'results.size', - level: 'extended', - type: 'long', - description: - 'The size of the result set displayed to the user. This should be\nequivalent to the length of the results in `results.ids`. This is also known\nas the page size or limit.', - example: 10, - default_field: false, - }, - { - name: 'results.total', - level: 'extended', - type: 'long', - description: - 'The total number of matches for this query. This number is always\ngreater than or equal to `results.size`. This is the `hits.total` field in\nthe query response.', - example: 134509, - default_field: false, - }, - ], - }, - { - name: 'server', - title: 'Server', - group: 2, - description: - 'A Server is defined as the responder in a network connection for\nevents regarding sessions, connections, or bidirectional flow records.\n\nFor TCP events, the server is the receiver of the initial SYN packet(s) of the\nTCP connection. For other protocols, the server is generally the responder in\nthe network transaction. Some systems actually use the term "responder" to refer\nthe server in TCP connections. The server fields describe details about the\nsystem acting as the server in the network event. Server fields are usually\npopulated in conjunction with client fields. Server fields are generally not\npopulated for packet-level events.\n\nClient / server representations can add semantic context to an exchange, which\nis helpful to visualize the data in certain situations. If your context falls\nin that category, you should still ensure that source and destination are filled\nappropriately.', - type: 'group', - fields: [ - { - name: 'address', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Some event server addresses are defined ambiguously. The event\nwill sometimes list an IP, a domain or a unix socket. You should always store\nthe raw address in the `.address` field.\n\nThen it should be duplicated to `.ip` or `.domain`, depending on which one\nit is.', - }, - { - name: 'as.number', - level: 'extended', - type: 'long', - description: - 'Unique number allocated to the autonomous system. The autonomous\nsystem number (ASN) uniquely identifies each network on the Internet.', - example: 15169, - }, - { - name: 'as.organization.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Organization name.', - example: 'Google LLC', - }, - { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - description: 'Bytes sent from the server to the client.', - example: 184, - }, - { - name: 'domain', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Server domain.', - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: 'IP address of the server (IPv4 or IPv6).', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'MAC address of the server.', - }, - { - name: 'nat.ip', - level: 'extended', - type: 'ip', - description: - 'Translated ip of destination based NAT sessions (e.g. internet\nto private DMZ)\n\nTypically used with load balancers, firewalls, or routers.', - }, - { - name: 'nat.port', - level: 'extended', - type: 'long', - format: 'string', - description: - 'Translated port of destination based NAT sessions (e.g. internet\nto private DMZ)\n\nTypically used with load balancers, firewalls, or routers.', - }, - { - name: 'packets', - level: 'core', - type: 'long', - description: 'Packets sent from the server to the client.', - example: 12, - }, - { - name: 'port', - level: 'core', - type: 'long', - format: 'string', - description: 'Port of the server.', - }, - { - name: 'registered_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The highest registered server domain, stripped of the subdomain.\n\nFor example, the registered domain for "foo.google.com" is "google.com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last two labels will not work well for TLDs such as "co.uk".', - example: 'google.com', - }, - { - name: 'top_level_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The effective top level domain (eTLD), also known as the domain\nsuffix, is the last part of the domain name. For example, the top level domain\nfor google.com is "com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last label will not work well for effective TLDs such as "co.uk".', - example: 'co.uk', - }, - { - name: 'user.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the user is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.email', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'User email address.', - }, - { - name: 'user.full_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'user.group.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.group.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'user.group.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - { - name: 'user.hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'user.id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier of the user.', - }, - { - name: 'user.name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'service', - title: 'Service', - group: 2, - description: - 'The service fields describe the service for or from which the data\nwas collected.\n\nThese fields help you find and correlate logs for a specific service and version.', - type: 'group', - fields: [ - { - name: 'ephemeral_id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Ephemeral identifier of this service (if one exists).\n\nThis id normally changes across restarts, but `service.id` does not.', - example: '8a4f500f', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique identifier of the running service. If the service is comprised\nof many nodes, the `service.id` should be the same for all nodes.\n\nThis id should uniquely identify the service. This makes it possible to correlate\nlogs and metrics for one specific service, no matter which particular node\nemitted the event.\n\nNote that if you need to see the events from one specific host of the service,\nyou should filter on that `host.name` or `host.id` instead.', - example: 'd37e5ebfe0ae6c4972dbe9f0174a1637bb8247f6', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the service data is collected from.\n\nThe name of the service is normally user given. This allows for distributed\nservices that run on multiple hosts to correlate the related instances based\non the name.\n\nIn the case of Elasticsearch the `service.name` could contain the cluster\nname. For Beats the `service.name` is by default a copy of the `service.type`\nfield if no name is specified.', - example: 'elasticsearch-metrics', - }, - { - name: 'node.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of a service node.\n\nThis allows for two nodes of the same service running on the same host to\nbe differentiated. Therefore, `service.node.name` should typically be unique\nacross nodes of a given service.\n\nIn the case of Elasticsearch, the `service.node.name` could contain the unique\nnode name within the Elasticsearch cluster. In cases where the service does not\nhave the concept of a node name, the host name or container name can be used\nto distinguish running instances that make up this service. If those do not\nprovide uniqueness (e.g. multiple instances of the service running on the\nsame host) - the node name can be manually set.', - example: 'instance-0000000016', - }, - { - name: 'state', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Current state of the service.', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'The type of the service data is collected from.\n\nThe type can be used to group and correlate logs and metrics from one service\ntype.\n\nExample: If logs or metrics are collected from Elasticsearch, `service.type`\nwould be `elasticsearch`.', - example: 'elasticsearch', - }, - { - name: 'version', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Version of the service the data was collected from.\n\nThis allows to look at a data set only for a specific version of a service.', - example: '3.2.4', - }, - ], - }, - { - name: 'source', - title: 'Source', - group: 2, - description: - 'Source fields describe details about the source of a packet/event.\n\nSource fields are usually populated in conjunction with destination fields.', - type: 'group', - fields: [ - { - name: 'address', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Some event source addresses are defined ambiguously. The event\nwill sometimes list an IP, a domain or a unix socket. You should always store\nthe raw address in the `.address` field.\n\nThen it should be duplicated to `.ip` or `.domain`, depending on which one\nit is.', - }, - { - name: 'as.number', - level: 'extended', - type: 'long', - description: - 'Unique number allocated to the autonomous system. The autonomous\nsystem number (ASN) uniquely identifies each network on the Internet.', - example: 15169, - }, - { - name: 'as.organization.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Organization name.', - example: 'Google LLC', - }, - { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - description: 'Bytes sent from the source to the destination.', - example: 184, - }, - { - name: 'domain', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Source domain.', - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: 'IP address of the source (IPv4 or IPv6).', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'MAC address of the source.', - }, - { - name: 'nat.ip', - level: 'extended', - type: 'ip', - description: - 'Translated ip of source based NAT sessions (e.g. internal client\nto internet)\n\nTypically connections traversing load balancers, firewalls, or routers.', - }, - { - name: 'nat.port', - level: 'extended', - type: 'long', - format: 'string', - description: - 'Translated port of source based NAT sessions. (e.g. internal client\nto internet)\n\nTypically used with load balancers, firewalls, or routers.', - }, - { - name: 'packets', - level: 'core', - type: 'long', - description: 'Packets sent from the source to the destination.', - example: 12, - }, - { - name: 'port', - level: 'core', - type: 'long', - format: 'string', - description: 'Port of the source.', - }, - { - name: 'registered_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The highest registered source domain, stripped of the subdomain.\n\nFor example, the registered domain for "foo.google.com" is "google.com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last two labels will not work well for TLDs such as "co.uk".', - example: 'google.com', - }, - { - name: 'top_level_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The effective top level domain (eTLD), also known as the domain\nsuffix, is the last part of the domain name. For example, the top level domain\nfor google.com is "com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last label will not work well for effective TLDs such as "co.uk".', - example: 'co.uk', - }, - { - name: 'user.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the user is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.email', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'User email address.', - }, - { - name: 'user.full_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'user.group.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.group.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'user.group.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - { - name: 'user.hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'user.id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier of the user.', - }, - { - name: 'user.name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'threat', - title: 'Threat', - group: 2, - description: - 'Fields to classify events and alerts according to a threat taxonomy\nsuch as the Mitre ATT&CK framework.\n\nThese fields are for users to classify alerts from all of their sources (e.g.\nIDS, NGFW, etc.) within a common taxonomy. The threat.tactic.* are meant to\ncapture the high level category of the threat (e.g. "impact"). The threat.technique.*\nfields are meant to capture which kind of approach is used by this detected\nthreat, to accomplish the goal (e.g. "endpoint denial of service").', - type: 'group', - fields: [ - { - name: 'framework', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the threat framework used to further categorize and classify\nthe tactic and technique of the reported threat. Framework classification\ncan be provided by detecting systems, evaluated at ingest time, or retrospectively\ntagged to events.', - example: 'MITRE ATT&CK', - }, - { - name: 'tactic.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The id of tactic used by this threat. You can use the Mitre ATT&CK\nMatrix Tactic categorization, for example. (ex. https://attack.mitre.org/tactics/TA0040/\n)', - example: 'TA0040', - }, - { - name: 'tactic.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the type of tactic used by this threat. You can use the\nMitre ATT&CK Matrix Tactic categorization, for example. (ex. https://attack.mitre.org/tactics/TA0040/\n)', - example: 'impact', - }, - { - name: 'tactic.reference', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The reference url of tactic used by this threat. You can use the\nMitre ATT&CK Matrix Tactic categorization, for example. (ex. https://attack.mitre.org/tactics/TA0040/\n)', - example: 'https://attack.mitre.org/tactics/TA0040/', - }, - { - name: 'technique.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The id of technique used by this tactic. You can use the Mitre\nATT&CK Matrix Tactic categorization, for example. (ex. https://attack.mitre.org/techniques/T1499/\n)', - example: 'T1499', - }, - { - name: 'technique.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: - 'The name of technique used by this tactic. You can use the Mitre\nATT&CK Matrix Tactic categorization, for example. (ex. https://attack.mitre.org/techniques/T1499/\n)', - example: 'endpoint denial of service', - }, - { - name: 'technique.reference', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The reference url of technique used by this tactic. You can use\nthe Mitre ATT&CK Matrix Tactic categorization, for example. (ex. https://attack.mitre.org/techniques/T1499/\n)', - example: 'https://attack.mitre.org/techniques/T1499/', - }, - ], - }, - { - name: 'tls', - title: 'TLS', - group: 2, - description: - 'Fields related to a TLS connection. These fields focus on the TLS\nprotocol itself and intentionally avoids in-depth analysis of the related x.509\ncertificate files.', - type: 'group', - fields: [ - { - name: 'cipher', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'String indicating the cipher used during the current connection.', - example: 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256', - default_field: false, - }, - { - name: 'client.certificate', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'PEM-encoded stand-alone certificate offered by the client. This\nis usually mutually-exclusive of `client.certificate_chain` since this value\nalso exists in that list.', - example: 'MII...', - default_field: false, - }, - { - name: 'client.certificate_chain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Array of PEM-encoded certificates that make up the certificate\nchain offered by the client. This is usually mutually-exclusive of `client.certificate`\nsince that value should be the first certificate in the chain.', - example: ['MII...', 'MII...'], - default_field: false, - }, - { - name: 'client.hash.md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Certificate fingerprint using the MD5 digest of DER-encoded version\nof certificate offered by the client. For consistency with other hash values,\nthis value should be formatted as an uppercase hash.', - example: '0F76C7F2C55BFD7D8E8B8F4BFBF0C9EC', - default_field: false, - }, - { - name: 'client.hash.sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Certificate fingerprint using the SHA1 digest of DER-encoded version\nof certificate offered by the client. For consistency with other hash values,\nthis value should be formatted as an uppercase hash.', - example: '9E393D93138888D288266C2D915214D1D1CCEB2A', - default_field: false, - }, - { - name: 'client.hash.sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Certificate fingerprint using the SHA256 digest of DER-encoded\nversion of certificate offered by the client. For consistency with other hash\nvalues, this value should be formatted as an uppercase hash.', - example: '0687F666A054EF17A08E2F2162EAB4CBC0D265E1D7875BE74BF3C712CA92DAF0', - default_field: false, - }, - { - name: 'client.issuer', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Distinguished name of subject of the issuer of the x.509 certificate\npresented by the client.', - example: 'CN=MyDomain Root CA, OU=Infrastructure Team, DC=mydomain, DC=com', - default_field: false, - }, - { - name: 'client.ja3', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A hash that identifies clients based on how they perform an SSL/TLS\nhandshake.', - example: 'd4e5b18d6b55c71272893221c96ba240', - default_field: false, - }, - { - name: 'client.not_after', - level: 'extended', - type: 'date', - description: - 'Date/Time indicating when client certificate is no longer considered\nvalid.', - example: '2021-01-01T00:00:00.000Z', - default_field: false, - }, - { - name: 'client.not_before', - level: 'extended', - type: 'date', - description: 'Date/Time indicating when client certificate is first considered\nvalid.', - example: '1970-01-01T00:00:00.000Z', - default_field: false, - }, - { - name: 'client.server_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Also called an SNI, this tells the server which hostname to which\nthe client is attempting to connect. When this value is available, it should\nget copied to `destination.domain`.', - example: 'www.elastic.co', - default_field: false, - }, - { - name: 'client.subject', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Distinguished name of subject of the x.509 certificate presented\nby the client.', - example: 'CN=myclient, OU=Documentation Team, DC=mydomain, DC=com', - default_field: false, - }, - { - name: 'client.supported_ciphers', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Array of ciphers offered by the client during the client hello.', - example: [ - 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384', - 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384', - '...', - ], - default_field: false, - }, - { - name: 'curve', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'String indicating the curve used for the given cipher, when applicable.', - example: 'secp256r1', - default_field: false, - }, - { - name: 'established', - level: 'extended', - type: 'boolean', - description: - 'Boolean flag indicating if the TLS negotiation was successful and\ntransitioned to an encrypted tunnel.', - default_field: false, - }, - { - name: 'next_protocol', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'String indicating the protocol being tunneled. Per the values in\nthe IANA registry (https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids),\nthis string should be lower case.', - example: 'http/1.1', - default_field: false, - }, - { - name: 'resumed', - level: 'extended', - type: 'boolean', - description: - 'Boolean flag indicating if this TLS connection was resumed from\nan existing TLS negotiation.', - default_field: false, - }, - { - name: 'server.certificate', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'PEM-encoded stand-alone certificate offered by the server. This\nis usually mutually-exclusive of `server.certificate_chain` since this value\nalso exists in that list.', - example: 'MII...', - default_field: false, - }, - { - name: 'server.certificate_chain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Array of PEM-encoded certificates that make up the certificate\nchain offered by the server. This is usually mutually-exclusive of `server.certificate`\nsince that value should be the first certificate in the chain.', - example: ['MII...', 'MII...'], - default_field: false, - }, - { - name: 'server.hash.md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Certificate fingerprint using the MD5 digest of DER-encoded version\nof certificate offered by the server. For consistency with other hash values,\nthis value should be formatted as an uppercase hash.', - example: '0F76C7F2C55BFD7D8E8B8F4BFBF0C9EC', - default_field: false, - }, - { - name: 'server.hash.sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Certificate fingerprint using the SHA1 digest of DER-encoded version\nof certificate offered by the server. For consistency with other hash values,\nthis value should be formatted as an uppercase hash.', - example: '9E393D93138888D288266C2D915214D1D1CCEB2A', - default_field: false, - }, - { - name: 'server.hash.sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Certificate fingerprint using the SHA256 digest of DER-encoded\nversion of certificate offered by the server. For consistency with other hash\nvalues, this value should be formatted as an uppercase hash.', - example: '0687F666A054EF17A08E2F2162EAB4CBC0D265E1D7875BE74BF3C712CA92DAF0', - default_field: false, - }, - { - name: 'server.issuer', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Subject of the issuer of the x.509 certificate presented by the\nserver.', - example: 'CN=MyDomain Root CA, OU=Infrastructure Team, DC=mydomain, DC=com', - default_field: false, - }, - { - name: 'server.ja3s', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A hash that identifies servers based on how they perform an SSL/TLS\nhandshake.', - example: '394441ab65754e2207b1e1b457b3641d', - default_field: false, - }, - { - name: 'server.not_after', - level: 'extended', - type: 'date', - description: - 'Timestamp indicating when server certificate is no longer considered\nvalid.', - example: '2021-01-01T00:00:00.000Z', - default_field: false, - }, - { - name: 'server.not_before', - level: 'extended', - type: 'date', - description: 'Timestamp indicating when server certificate is first considered\nvalid.', - example: '1970-01-01T00:00:00.000Z', - default_field: false, - }, - { - name: 'server.subject', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Subject of the x.509 certificate presented by the server.', - example: 'CN=www.mydomain.com, OU=Infrastructure Team, DC=mydomain, DC=com', - default_field: false, - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Numeric part of the version parsed from the original string.', - example: '1.2', - default_field: false, - }, - { - name: 'version_protocol', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Normalized lowercase protocol name parsed from original string.', - example: 'tls', - default_field: false, - }, - ], - }, - { - name: 'tracing', - title: 'Tracing', - group: 2, - description: - 'Distributed tracing makes it possible to analyze performance throughout\na microservice architecture all in one view. This is accomplished by tracing\nall of the requests - from the initial web request in the front-end service\n- to queries made through multiple back-end services.', - type: 'group', - fields: [ - { - name: 'trace.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique identifier of the trace.\n\nA trace groups multiple events like transactions that belong together. For\nexample, a user request handled by multiple inter-connected services.', - example: '4bf92f3577b34da6a3ce929d0e0e4736', - }, - { - name: 'transaction.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique identifier of the transaction.\n\nA transaction is the highest level of work measured within a service, such\nas a request to a server.', - example: '00f067aa0ba902b7', - }, - ], - }, - { - name: 'url', - title: 'URL', - group: 2, - description: - 'URL fields provide support for complete or partial URLs, and supports\nthe breaking down into scheme, domain, path, and so on.', - type: 'group', - fields: [ - { - name: 'domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Domain of the url, such as "www.elastic.co".\n\nIn some cases a URL may refer to an IP and/or port directly, without a domain\nname. In this case, the IP address would go to the `domain` field.', - example: 'www.elastic.co', - }, - { - name: 'extension', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The field contains the file extension from the original request\nurl.\n\nThe file extension is only set if it exists, as not every url has a file extension.\n\nThe leading period must not be included. For example, the value must be "png",\nnot ".png".', - example: 'png', - }, - { - name: 'fragment', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Portion of the url after the `#`, such as "top".\n\nThe `#` is not part of the fragment.', - }, - { - name: 'full', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: - 'If full URLs are important to your use case, they should be stored\nin `url.full`, whether this field is reconstructed or present in the event\nsource.', - example: 'https://www.elastic.co:443/search?q=elasticsearch#top', - }, - { - name: 'original', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: - 'Unmodified original url as seen in the event source.\n\nNote that in network monitoring, the observed URL may be a full URL, whereas\nin access logs, the URL is often just represented as a path.\n\nThis field is meant to represent the URL as it was observed, complete or not.', - example: - 'https://www.elastic.co:443/search?q=elasticsearch#top or /search?q=elasticsearch', - }, - { - name: 'password', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Password of the request.', - }, - { - name: 'path', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Path of the request, such as "/search".', - }, - { - name: 'port', - level: 'extended', - type: 'long', - format: 'string', - description: 'Port of the request, such as 443.', - example: 443, - }, - { - name: 'query', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The query field describes the query string of the request, such\nas "q=elasticsearch".\n\nThe `?` is excluded from the query string. If a URL contains no `?`, there\nis no query field. If there is a `?` but no query, the query field exists\nwith an empty string. The `exists` query can be used to differentiate between\nthe two cases.', - }, - { - name: 'registered_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The highest registered url domain, stripped of the subdomain.\n\nFor example, the registered domain for "foo.google.com" is "google.com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last two labels will not work well for TLDs such as "co.uk".', - example: 'google.com', - }, - { - name: 'scheme', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Scheme of the request, such as "https".\n\nNote: The `:` is not part of the scheme.', - example: 'https', - }, - { - name: 'top_level_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The effective top level domain (eTLD), also known as the domain\nsuffix, is the last part of the domain name. For example, the top level domain\nfor google.com is "com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last label will not work well for effective TLDs such as "co.uk".', - example: 'co.uk', - }, - { - name: 'username', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Username of the request.', - }, - ], - }, - { - name: 'user', - title: 'User', - group: 2, - description: - 'The user fields describe information about the user that is relevant\nto the event.\n\nFields can have one entry or multiple entries. If a user has more than one id,\nprovide an array that includes all of them.', - type: 'group', - fields: [ - { - name: 'domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the user is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'email', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'User email address.', - }, - { - name: 'full_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'group.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'group.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'group.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - { - name: 'hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier of the user.', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'user_agent', - title: 'User agent', - group: 2, - description: - 'The user_agent fields normally come from a browser request.\n\nThey often show up in web service logs coming from the parsed user agent string.', - type: 'group', - fields: [ - { - name: 'device.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the device.', - example: 'iPhone', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the user agent.', - example: 'Safari', - }, - { - name: 'original', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: 'Unparsed user_agent string.', - example: - 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15\n(KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1', - }, - { - name: 'os.family', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'OS family (such as redhat, debian, freebsd, windows).', - example: 'debian', - }, - { - name: 'os.full', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, including the version or code name.', - example: 'Mac OS Mojave', - }, - { - name: 'os.kernel', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system kernel version as a raw string.', - example: '4.4.0-112-generic', - }, - { - name: 'os.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, without the version.', - example: 'Mac OS X', - }, - { - name: 'os.platform', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'os.version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system version as a raw string.', - example: '10.14.1', - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Version of the user agent.', - example: 12, - }, - ], - }, - { - name: 'vlan', - title: 'VLAN', - group: 2, - description: - 'The VLAN fields are used to identify 802.1q tag(s) of a packet,\nas well as ingress and egress VLAN associations of an observer in relation to\na specific packet or connection.\n\nNetwork.vlan fields are used to record a single VLAN tag, or the outer tag in\nthe case of q-in-q encapsulations, for a packet or connection as observed, typically\nprovided by a network sensor (e.g. Zeek, Wireshark) passively reporting on traffic.\n\nNetwork.inner VLAN fields are used to report inner q-in-q 802.1q tags (multiple\n802.1q encapsulations) as observed, typically provided by a network sensor (e.g.\nZeek, Wireshark) passively reporting on traffic. Network.inner VLAN fields should\nonly be used in addition to network.vlan fields to indicate q-in-q tagging.\n\nObserver.ingress and observer.egress VLAN values are used to record observer\nspecific information when observer events contain discrete ingress and egress\nVLAN information, typically provided by firewalls, routers, or load balancers.', - type: 'group', - fields: [ - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'VLAN ID as reported by the observer.', - example: 10, - default_field: false, - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Optional VLAN name as reported by the observer.', - example: 'outside', - default_field: false, - }, - ], - }, - { - name: 'vulnerability', - title: 'Vulnerability', - group: 2, - description: - 'The vulnerability fields describe information about a vulnerability\nthat is relevant to an event.', - type: 'group', - fields: [ - { - name: 'category', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The type of system or architecture that the vulnerability affects.\nThese may be platform-specific (for example, Debian or SUSE) or general (for\nexample, Database or Firewall). For example (https://qualysguard.qualys.com/qwebhelp/fo_portal/knowledgebase/vulnerability_categories.htm[Qualys\nvulnerability categories])\n\nThis field must be an array.', - example: '["Firewall"]', - default_field: false, - }, - { - name: 'classification', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The classification of the vulnerability scoring system. For example\n(https://www.first.org/cvss/)', - example: 'CVSS', - default_field: false, - }, - { - name: 'description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: - 'The description of the vulnerability that provides additional context\nof the vulnerability. For example (https://cve.mitre.org/about/faqs.html#cve_entry_descriptions_created[Common\nVulnerabilities and Exposure CVE description])', - example: 'In macOS before 2.12.6, there is a vulnerability in the RPC...', - default_field: false, - }, - { - name: 'enumeration', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The type of identifier used for this vulnerability. For example\n(https://cve.mitre.org/about/)', - example: 'CVE', - default_field: false, - }, - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The identification (ID) is the number portion of a vulnerability\nentry. It includes a unique identification number for the vulnerability. For\nexample (https://cve.mitre.org/about/faqs.html#what_is_cve_id)[Common Vulnerabilities\nand Exposure CVE ID]', - example: 'CVE-2019-00001', - default_field: false, - }, - { - name: 'reference', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A resource that provides additional information, context, and mitigations\nfor the identified vulnerability.', - example: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-6111', - default_field: false, - }, - { - name: 'report_id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The report or scan identification number.', - example: 20191018.0001, - default_field: false, - }, - { - name: 'scanner.vendor', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The name of the vulnerability scanner vendor.', - example: 'Tenable', - default_field: false, - }, - { - name: 'score.base', - level: 'extended', - type: 'float', - description: - 'Scores can range from 0.0 to 10.0, with 10.0 being the most severe.\n\nBase scores cover an assessment for exploitability metrics (attack vector,\ncomplexity, privileges, and user interaction), impact metrics (confidentiality,\nintegrity, and availability), and scope. For example (https://www.first.org/cvss/specification-document)', - example: 5.5, - default_field: false, - }, - { - name: 'score.environmental', - level: 'extended', - type: 'float', - description: - 'Scores can range from 0.0 to 10.0, with 10.0 being the most severe.\n\nEnvironmental scores cover an assessment for any modified Base metrics, confidentiality,\nintegrity, and availability requirements. For example (https://www.first.org/cvss/specification-document)', - example: 5.5, - default_field: false, - }, - { - name: 'score.temporal', - level: 'extended', - type: 'float', - description: - 'Scores can range from 0.0 to 10.0, with 10.0 being the most severe.\n\nTemporal scores cover an assessment for code maturity, remediation level,\nand confidence. For example (https://www.first.org/cvss/specification-document)', - default_field: false, - }, - { - name: 'score.version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The National Vulnerability Database (NVD) provides qualitative\nseverity rankings of "Low", "Medium", and "High" for CVSS v2.0 base score\nranges in addition to the severity ratings for CVSS v3.0 as they are defined\nin the CVSS v3.0 specification.\n\nCVSS is owned and managed by FIRST.Org, Inc. (FIRST), a US-based non-profit\norganization, whose mission is to help computer security incident response\nteams across the world. For example (https://nvd.nist.gov/vuln-metrics/cvss)', - example: 2, - default_field: false, - }, - { - name: 'severity', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The severity of the vulnerability can help with metrics and internal\nprioritization regarding remediation. For example (https://nvd.nist.gov/vuln-metrics/cvss)', - example: 'Critical', - default_field: false, - }, - ], - }, - ], - }, -]; diff --git a/x-pack/plugins/security_solution/server/utils/beat_schema/8.0.0/filebeat.ts b/x-pack/plugins/security_solution/server/utils/beat_schema/8.0.0/filebeat.ts deleted file mode 100644 index 3b8c92ebba269..0000000000000 --- a/x-pack/plugins/security_solution/server/utils/beat_schema/8.0.0/filebeat.ts +++ /dev/null @@ -1,21243 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -/** - * An instance of the unmodified schema exported from filebeat-8.0.0-SNAPSHOT-darwin-x86_64.tar.gz - * - */ - -import { Schema } from '../type'; - -export const filebeatSchema: Schema = [ - { - key: 'ecs', - title: 'ECS', - description: 'ECS Fields.', - fields: [ - { - name: '@timestamp', - level: 'core', - required: true, - type: 'date', - description: - 'Date/time when the event originated.\n\nThis is the date/time extracted from the event, typically representing when\nthe event was generated by the source.\n\nIf the event source has no original timestamp, this value is typically populated\nby the first time the event was received by the pipeline.\n\nRequired field for all events.', - example: '2016-05-23T08:05:34.853Z', - }, - { - name: 'labels', - level: 'core', - type: 'object', - object_type: 'keyword', - description: - 'Custom key/value pairs.\n\nCan be used to add meta information to events. Should not contain nested objects.\nAll values are stored as keyword.\n\nExample: `docker` and `k8s` labels.', - example: '{"application": "foo-bar", "env": "production"}', - }, - { - name: 'message', - level: 'core', - type: 'text', - description: - 'For log events the message field contains the log message, optimized\nfor viewing in a log viewer.\n\nFor structured logs without an original message field, other fields can be concatenated\nto form a human-readable summary of the event.\n\nIf multiple messages exist, they can be combined into one message.', - example: 'Hello World', - }, - { - name: 'tags', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'List of keywords used to tag each event.', - example: '["production", "env2"]', - }, - { - name: 'agent', - title: 'Agent', - group: 2, - description: - 'The agent fields contain the data about the software entity, if\nany, that collects, detects, or observes events on a host, or takes measurements\non a host.\n\nExamples include Beats. Agents may also run on observers. ECS agent.* fields\nshall be populated with details of the agent running on the host or observer\nwhere the event happened or the measurement was taken.', - footnote: - 'Examples: In the case of Beats for logs, the agent.name is filebeat.\nFor APM, it is the agent running in the app/service. The agent information does\nnot change if data is sent through queuing systems like Kafka, Redis, or processing\nsystems such as Logstash or APM Server.', - type: 'group', - fields: [ - { - name: 'ephemeral_id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Ephemeral identifier of this agent (if one exists).\n\nThis id normally changes across restarts, but `agent.id` does not.', - example: '8a4f500f', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique identifier of this agent (if one exists).\n\nExample: For Beats this would be beat.id.', - example: '8a4f500d', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Custom name of the agent.\n\nThis is a name that can be given to an agent. This can be helpful if for example\ntwo Filebeat instances are running on the same host but a human readable separation\nis needed on which Filebeat instance data is coming from.\n\nIf no name is given, the name is often left empty.', - example: 'foo', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Type of the agent.\n\nThe agent type stays always the same and should be given by the agent used.\nIn case of Filebeat the agent would always be Filebeat also if two Filebeat\ninstances are run on the same machine.', - example: 'filebeat', - }, - { - name: 'version', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Version of the agent.', - example: '6.0.0-rc2', - }, - ], - }, - { - name: 'as', - title: 'Autonomous System', - group: 2, - description: - 'An autonomous system (AS) is a collection of connected Internet Protocol\n(IP) routing prefixes under the control of one or more network operators on\nbehalf of a single administrative entity or domain that presents a common, clearly\ndefined routing policy to the internet.', - type: 'group', - fields: [ - { - name: 'number', - level: 'extended', - type: 'long', - description: - 'Unique number allocated to the autonomous system. The autonomous\nsystem number (ASN) uniquely identifies each network on the Internet.', - example: 15169, - }, - { - name: 'organization.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Organization name.', - example: 'Google LLC', - }, - ], - }, - { - name: 'client', - title: 'Client', - group: 2, - description: - 'A client is defined as the initiator of a network connection for\nevents regarding sessions, connections, or bidirectional flow records.\n\nFor TCP events, the client is the initiator of the TCP connection that sends\nthe SYN packet(s). For other protocols, the client is generally the initiator\nor requestor in the network transaction. Some systems use the term "originator"\nto refer the client in TCP connections. The client fields describe details about\nthe system acting as the client in the network event. Client fields are usually\npopulated in conjunction with server fields. Client fields are generally not\npopulated for packet-level events.\n\nClient / server representations can add semantic context to an exchange, which\nis helpful to visualize the data in certain situations. If your context falls\nin that category, you should still ensure that source and destination are filled\nappropriately.', - type: 'group', - fields: [ - { - name: 'address', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Some event client addresses are defined ambiguously. The event\nwill sometimes list an IP, a domain or a unix socket. You should always store\nthe raw address in the `.address` field.\n\nThen it should be duplicated to `.ip` or `.domain`, depending on which one\nit is.', - }, - { - name: 'as.number', - level: 'extended', - type: 'long', - description: - 'Unique number allocated to the autonomous system. The autonomous\nsystem number (ASN) uniquely identifies each network on the Internet.', - example: 15169, - }, - { - name: 'as.organization.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Organization name.', - example: 'Google LLC', - }, - { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - description: 'Bytes sent from the client to the server.', - example: 184, - }, - { - name: 'domain', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Client domain.', - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: - 'IP address of the client.\n\nCan be one or multiple IPv4 or IPv6 addresses.', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'MAC address of the client.', - }, - { - name: 'nat.ip', - level: 'extended', - type: 'ip', - description: - 'Translated IP of source based NAT sessions (e.g. internal client\nto internet).\n\nTypically connections traversing load balancers, firewalls, or routers.', - }, - { - name: 'nat.port', - level: 'extended', - type: 'long', - format: 'string', - description: - 'Translated port of source based NAT sessions (e.g. internal client\nto internet).\n\nTypically connections traversing load balancers, firewalls, or routers.', - }, - { - name: 'packets', - level: 'core', - type: 'long', - description: 'Packets sent from the client to the server.', - example: 12, - }, - { - name: 'port', - level: 'core', - type: 'long', - format: 'string', - description: 'Port of the client.', - }, - { - name: 'registered_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The highest registered client domain, stripped of the subdomain.\n\nFor example, the registered domain for "foo.google.com" is "google.com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last two labels will not work well for TLDs such as "co.uk".', - example: 'google.com', - }, - { - name: 'top_level_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The effective top level domain (eTLD), also known as the domain\nsuffix, is the last part of the domain name. For example, the top level domain\nfor google.com is "com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last label will not work well for effective TLDs such as "co.uk".', - example: 'co.uk', - }, - { - name: 'user.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the user is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.email', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'User email address.', - }, - { - name: 'user.full_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'user.group.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.group.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'user.group.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - { - name: 'user.hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'user.id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifiers of the user.', - }, - { - name: 'user.name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'cloud', - title: 'Cloud', - group: 2, - description: 'Fields related to the cloud or infrastructure the events are coming\nfrom.', - footnote: - 'Examples: If Metricbeat is running on an EC2 host and fetches data\nfrom its host, the cloud info contains the data about this machine. If Metricbeat\nruns on a remote machine outside the cloud and fetches data from a service running\nin the cloud, the field contains cloud data from the machine the service is\nrunning on.', - type: 'group', - fields: [ - { - name: 'account.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The cloud account or organization id used to identify different\nentities in a multi-tenant environment.\n\nExamples: AWS account id, Google Cloud ORG Id, or other unique identifier.', - example: 666777888999, - }, - { - name: 'availability_zone', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Availability zone in which this host is running.', - example: 'us-east-1c', - }, - { - name: 'instance.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Instance ID of the host machine.', - example: 'i-1234567890abcdef0', - }, - { - name: 'instance.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Instance name of the host machine.', - }, - { - name: 'machine.type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Machine type of the host machine.', - example: 't2.medium', - }, - { - name: 'provider', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the cloud provider. Example values are aws, azure, gcp,\nor digitalocean.', - example: 'aws', - }, - { - name: 'region', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Region in which this host is running.', - example: 'us-east-1', - }, - ], - }, - { - name: 'code_signature', - title: 'Code Signature', - group: 2, - description: 'These fields contain information about binary code signatures.', - type: 'group', - fields: [ - { - name: 'exists', - level: 'core', - type: 'boolean', - description: 'Boolean to capture if a signature is present.', - example: 'true', - default_field: false, - }, - { - name: 'status', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Additional information about the certificate status.\n\nThis is useful for logging cryptographic errors with the certificate validity\nor trust status. Leave unpopulated if the validity or trust of the certificate\nwas unchecked.', - example: 'ERROR_UNTRUSTED_ROOT', - default_field: false, - }, - { - name: 'subject_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Subject name of the code signer', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'trusted', - level: 'extended', - type: 'boolean', - description: - 'Stores the trust status of the certificate chain.\n\nValidating the trust of the certificate chain may be complicated, and this\nfield should only be populated by tools that actively check the status.', - example: 'true', - default_field: false, - }, - { - name: 'valid', - level: 'extended', - type: 'boolean', - description: - 'Boolean to capture if the digital signature is verified against\nthe binary content.\n\nLeave unpopulated if a certificate was unchecked.', - example: 'true', - default_field: false, - }, - ], - }, - { - name: 'container', - title: 'Container', - group: 2, - description: - 'Container fields are used for meta information about the specific\ncontainer that is the source of information.\n\nThese fields help correlate data based containers from any runtime.', - type: 'group', - fields: [ - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique container id.', - }, - { - name: 'image.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the image the container was built on.', - }, - { - name: 'image.tag', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Container image tags.', - }, - { - name: 'labels', - level: 'extended', - type: 'object', - object_type: 'keyword', - description: 'Image labels.', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Container name.', - }, - { - name: 'runtime', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Runtime managing this container.', - example: 'docker', - }, - ], - }, - { - name: 'destination', - title: 'Destination', - group: 2, - description: - 'Destination fields describe details about the destination of a packet/event.\n\nDestination fields are usually populated in conjunction with source fields.', - type: 'group', - fields: [ - { - name: 'address', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Some event destination addresses are defined ambiguously. The\nevent will sometimes list an IP, a domain or a unix socket. You should always\nstore the raw address in the `.address` field.\n\nThen it should be duplicated to `.ip` or `.domain`, depending on which one\nit is.', - }, - { - name: 'as.number', - level: 'extended', - type: 'long', - description: - 'Unique number allocated to the autonomous system. The autonomous\nsystem number (ASN) uniquely identifies each network on the Internet.', - example: 15169, - }, - { - name: 'as.organization.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Organization name.', - example: 'Google LLC', - }, - { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - description: 'Bytes sent from the destination to the source.', - example: 184, - }, - { - name: 'domain', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Destination domain.', - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: - 'IP address of the destination.\n\nCan be one or multiple IPv4 or IPv6 addresses.', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'MAC address of the destination.', - }, - { - name: 'nat.ip', - level: 'extended', - type: 'ip', - description: - 'Translated ip of destination based NAT sessions (e.g. internet\nto private DMZ)\n\nTypically used with load balancers, firewalls, or routers.', - }, - { - name: 'nat.port', - level: 'extended', - type: 'long', - format: 'string', - description: - 'Port the source session is translated to by NAT Device.\n\nTypically used with load balancers, firewalls, or routers.', - }, - { - name: 'packets', - level: 'core', - type: 'long', - description: 'Packets sent from the destination to the source.', - example: 12, - }, - { - name: 'port', - level: 'core', - type: 'long', - format: 'string', - description: 'Port of the destination.', - }, - { - name: 'registered_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The highest registered destination domain, stripped of the subdomain.\n\nFor example, the registered domain for "foo.google.com" is "google.com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last two labels will not work well for TLDs such as "co.uk".', - example: 'google.com', - }, - { - name: 'top_level_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The effective top level domain (eTLD), also known as the domain\nsuffix, is the last part of the domain name. For example, the top level domain\nfor google.com is "com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last label will not work well for effective TLDs such as "co.uk".', - example: 'co.uk', - }, - { - name: 'user.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the user is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.email', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'User email address.', - }, - { - name: 'user.full_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'user.group.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.group.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'user.group.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - { - name: 'user.hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'user.id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifiers of the user.', - }, - { - name: 'user.name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'dll', - title: 'DLL', - group: 2, - description: - 'These fields contain information about code libraries dynamically\nloaded into processes.\n\n\nMany operating systems refer to "shared code libraries" with different names,\nbut this field set refers to all of the following:\n\n* Dynamic-link library (`.dll`) commonly used on Windows\n\n* Shared Object (`.so`) commonly used on Unix-like operating systems\n\n* Dynamic library (`.dylib`) commonly used on macOS', - type: 'group', - fields: [ - { - name: 'code_signature.exists', - level: 'core', - type: 'boolean', - description: 'Boolean to capture if a signature is present.', - example: 'true', - default_field: false, - }, - { - name: 'code_signature.status', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Additional information about the certificate status.\n\nThis is useful for logging cryptographic errors with the certificate validity\nor trust status. Leave unpopulated if the validity or trust of the certificate\nwas unchecked.', - example: 'ERROR_UNTRUSTED_ROOT', - default_field: false, - }, - { - name: 'code_signature.subject_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Subject name of the code signer', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'code_signature.trusted', - level: 'extended', - type: 'boolean', - description: - 'Stores the trust status of the certificate chain.\n\nValidating the trust of the certificate chain may be complicated, and this\nfield should only be populated by tools that actively check the status.', - example: 'true', - default_field: false, - }, - { - name: 'code_signature.valid', - level: 'extended', - type: 'boolean', - description: - 'Boolean to capture if the digital signature is verified against\nthe binary content.\n\nLeave unpopulated if a certificate was unchecked.', - example: 'true', - default_field: false, - }, - { - name: 'hash.md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'MD5 hash.', - default_field: false, - }, - { - name: 'hash.sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA1 hash.', - default_field: false, - }, - { - name: 'hash.sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA256 hash.', - default_field: false, - }, - { - name: 'hash.sha512', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA512 hash.', - default_field: false, - }, - { - name: 'name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the library.\n\nThis generally maps to the name of the file on disk.', - example: 'kernel32.dll', - default_field: false, - }, - { - name: 'path', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Full file path of the library.', - example: 'C:\\Windows\\System32\\kernel32.dll', - default_field: false, - }, - { - name: 'pe.company', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal company name of the file, provided at compile-time.', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'pe.description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal description of the file, provided at compile-time.', - example: 'Paint', - default_field: false, - }, - { - name: 'pe.file_version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal version of the file, provided at compile-time.', - example: '6.3.9600.17415', - default_field: false, - }, - { - name: 'pe.original_file_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal name of the file, provided at compile-time.', - example: 'MSPAINT.EXE', - default_field: false, - }, - { - name: 'pe.product', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal product name of the file, provided at compile-time.', - example: 'Microsoft® Windows® Operating System', - default_field: false, - }, - ], - }, - { - name: 'dns', - title: 'DNS', - group: 2, - description: - 'Fields describing DNS queries and answers.\n\nDNS events should either represent a single DNS query prior to getting answers\n(`dns.type:query`) or they should represent a full exchange and contain the\nquery details as well as all of the answers that were provided for this query\n(`dns.type:answer`).', - type: 'group', - fields: [ - { - name: 'answers', - level: 'extended', - type: 'object', - object_type: 'keyword', - description: - 'An array containing an object for each answer section returned\nby the server.\n\nThe main keys that should be present in these objects are defined by ECS.\nRecords that have more information may contain more keys than what ECS defines.\n\nNot all DNS data sources give all details about DNS answers. At minimum, answer\nobjects must contain the `data` key. If more information is available, map\nas much of it to ECS as possible, and add any additional fields to the answer\nobjects as custom fields.', - }, - { - name: 'answers.class', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The class of DNS data contained in this resource record.', - example: 'IN', - }, - { - name: 'answers.data', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The data describing the resource.\n\nThe meaning of this data depends on the type and class of the resource record.', - example: '10.10.10.10', - }, - { - name: 'answers.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The domain name to which this resource record pertains.\n\nIf a chain of CNAME is being resolved, each answer `name` should be the\none that corresponds with the answer `data`. It should not simply be the\noriginal `question.name` repeated.', - example: 'www.google.com', - }, - { - name: 'answers.ttl', - level: 'extended', - type: 'long', - description: - 'The time interval in seconds that this resource record may be cached\nbefore it should be discarded. Zero values mean that the data should not be\ncached.', - example: 180, - }, - { - name: 'answers.type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The type of data contained in this resource record.', - example: 'CNAME', - }, - { - name: 'header_flags', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Array of 2 letter DNS header flags.\n\nExpected values are: AA, TC, RD, RA, AD, CD, DO.', - example: ['RD', 'RA'], - }, - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The DNS packet identifier assigned by the program that generated\nthe query. The identifier is copied to the response.', - example: 62111, - }, - { - name: 'op_code', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The DNS operation code that specifies the kind of query in the\nmessage. This value is set by the originator of a query and copied into the\nresponse.', - example: 'QUERY', - }, - { - name: 'question.class', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The class of records being queried.', - example: 'IN', - }, - { - name: 'question.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The name being queried.\n\nIf the name field contains non-printable characters (below 32 or above 126),\nthose characters should be represented as escaped base 10 integers (\\DDD).\nBack slashes and quotes should be escaped. Tabs, carriage returns, and line\nfeeds should be converted to \\t, \\r, and \\n respectively.', - example: 'www.google.com', - }, - { - name: 'question.registered_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The highest registered domain, stripped of the subdomain.\n\nFor example, the registered domain for "foo.google.com" is "google.com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last two labels will not work well for TLDs such as "co.uk".', - example: 'google.com', - }, - { - name: 'question.subdomain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The subdomain is all of the labels under the registered_domain.\n\nIf the domain has multiple levels of subdomain, such as "sub2.sub1.example.com",\nthe subdomain field should contain "sub2.sub1", with no trailing period.', - example: 'www', - }, - { - name: 'question.top_level_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The effective top level domain (eTLD), also known as the domain\nsuffix, is the last part of the domain name. For example, the top level domain\nfor google.com is "com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last label will not work well for effective TLDs such as "co.uk".', - example: 'co.uk', - }, - { - name: 'question.type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The type of record being queried.', - example: 'AAAA', - }, - { - name: 'resolved_ip', - level: 'extended', - type: 'ip', - description: - 'Array containing all IPs seen in `answers.data`.\n\nThe `answers` array can be difficult to use, because of the variety of data\nformats it can contain. Extracting all IP addresses seen in there to `dns.resolved_ip`\nmakes it possible to index them as IP addresses, and makes them easier to\nvisualize and query for.', - example: ['10.10.10.10', '10.10.10.11'], - }, - { - name: 'response_code', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The DNS response code.', - example: 'NOERROR', - }, - { - name: 'type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The type of DNS event captured, query or answer.\n\nIf your source of DNS events only gives you DNS queries, you should only create\ndns events of type `dns.type:query`.\n\nIf your source of DNS events gives you answers as well, you should create\none event per query (optionally as soon as the query is seen). And a second\nevent containing all query details as well as an array of answers.', - example: 'answer', - }, - ], - }, - { - name: 'ecs', - title: 'ECS', - group: 2, - description: 'Meta-information specific to ECS.', - type: 'group', - fields: [ - { - name: 'version', - level: 'core', - required: true, - type: 'keyword', - ignore_above: 1024, - description: - 'ECS version this event conforms to. `ecs.version` is a required\nfield and must exist in all events.\n\nWhen querying across multiple indices -- which may conform to slightly different\nECS versions -- this field lets integrations adjust to the schema version\nof the events.', - example: '1.0.0', - }, - ], - }, - { - name: 'error', - title: 'Error', - group: 2, - description: - 'These fields can represent errors of any kind.\n\nUse them for errors that happen while fetching events or in cases where the\nevent itself contains an error.', - type: 'group', - fields: [ - { - name: 'code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Error code describing the error.', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the error.', - }, - { - name: 'message', - level: 'core', - type: 'text', - description: 'Error message.', - }, - { - name: 'stack_trace', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'The stack trace of this error in plain text.', - }, - { - name: 'type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The type of the error, for example the class name of the exception.', - example: 'java.lang.NullPointerException', - }, - ], - }, - { - name: 'event', - title: 'Event', - group: 2, - description: - 'The event fields are used for context information about the log\nor metric event itself.\n\nA log is defined as an event containing details of something that happened.\nLog events must include the time at which the thing happened. Examples of log\nevents include a process starting on a host, a network packet being sent from\na source to a destination, or a network connection between a client and a server\nbeing initiated or closed. A metric is defined as an event containing one or\nmore numerical measurements and the time at which the measurement was taken.\nExamples of metric events include memory pressure measured on a host and device\ntemperature. See the `event.kind` definition in this section for additional\ndetails about metric and state events.', - type: 'group', - fields: [ - { - name: 'action', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'The action captured by the event.\n\nThis describes the information in the event. It is more specific than `event.category`.\nExamples are `group-add`, `process-started`, `file-created`. The value is\nnormally defined by the implementer.', - example: 'user-password-change', - }, - { - name: 'category', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'This is one of four ECS Categorization Fields, and indicates the\nsecond level in the ECS category hierarchy.\n\n`event.category` represents the "big buckets" of ECS categories. For example,\nfiltering on `event.category:process` yields all events relating to process\nactivity. This field is closely related to `event.type`, which is used as\na subcategory.\n\nThis field is an array. This will allow proper categorization of some events\nthat fall in multiple categories.', - example: 'authentication', - }, - { - name: 'code', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Identification code for this event, if one exists.\n\nSome event sources use event codes to identify messages unambiguously, regardless\nof message language or wording adjustments over time. An example of this is\nthe Windows Event ID.', - example: 4648, - }, - { - name: 'created', - level: 'core', - type: 'date', - description: - 'event.created contains the date/time when the event was first\nread by an agent, or by your pipeline.\n\nThis field is distinct from @timestamp in that @timestamp typically contain\nthe time extracted from the original event.\n\nIn most situations, these two timestamps will be slightly different. The difference\ncan be used to calculate the delay between your source generating an event,\nand the time when your agent first processed it. This can be used to monitor\nyour agent or pipeline ability to keep up with your event source.\n\nIn case the two timestamps are identical, @timestamp should be used.', - example: '2016-05-23T08:05:34.857Z', - }, - { - name: 'dataset', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the dataset.\n\nIf an event source publishes more than one type of log or events (e.g. access\nlog, error log), the dataset is used to specify which one the event comes\nfrom.\n\nIt is recommended but not required to start the dataset name with the module\nname, followed by a dot, then the dataset name.', - example: 'apache.access', - }, - { - name: 'duration', - level: 'core', - type: 'long', - format: 'duration', - input_format: 'nanoseconds', - output_format: 'asMilliseconds', - output_precision: 1, - description: - 'Duration of the event in nanoseconds.\n\nIf event.start and event.end are known this value should be the difference\nbetween the end and start time.', - }, - { - name: 'end', - level: 'extended', - type: 'date', - description: - 'event.end contains the date when the event ended or when the activity\nwas last observed.', - }, - { - name: 'hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Hash (perhaps logstash fingerprint) of raw field to be able to\ndemonstrate log integrity.', - example: '123456789012345678901234567890ABCD', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique ID to describe the event.', - example: '8a4f500d', - }, - { - name: 'ingested', - level: 'core', - type: 'date', - description: - 'Timestamp when an event arrived in the central data store.\n\nThis is different from `@timestamp`, which is when the event originally occurred. It is\nalso different from `event.created`, which is meant to capture the first time\nan agent saw the event.\n\nIn normal conditions, assuming no tampering, the timestamps should chronologically\nlook like this: `@timestamp` < `event.created` < `event.ingested`.', - example: '2016-05-23T08:05:35.101Z', - default_field: false, - }, - { - name: 'kind', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'This is one of four ECS Categorization Fields, and indicates the\nhighest level in the ECS category hierarchy.\n\n`event.kind` gives high-level information about what type of information the\nevent contains, without being specific to the contents of the event. For example,\nvalues of this field distinguish alert events from metric events.\n\nThe value of this field can be used to inform how these kinds of events should\nbe handled. They may warrant different retention, different access control,\nit may also help understand whether the data coming in at a regular interval\nor not.', - example: 'alert', - }, - { - name: 'module', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the module this data is coming from.\n\nIf your monitoring agent supports the concept of modules or plugins to process\nevents of a given source (e.g. Apache logs), `event.module` should contain\nthe name of this module.', - example: 'apache', - }, - { - name: 'original', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Raw text message of entire event. Used to demonstrate log integrity.\n\nThis field is not indexed and doc_values are disabled. It cannot be searched,\nbut it can be retrieved from `_source`.', - example: - 'Sep 19 08:26:10 host CEF:0|Security| threatmanager|1.0|100|\nworm successfully stopped|10|src=10.0.0.1 dst=2.1.2.2spt=1232', - }, - { - name: 'outcome', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'This is one of four ECS Categorization Fields, and indicates the\nlowest level in the ECS category hierarchy.\n\n`event.outcome` simply denotes whether the event represents a success or a\nfailure from the perspective of the entity that produced the event.\n\nNote that when a single transaction is described in multiple events, each\nevent may populate different values of `event.outcome`, according to their\nperspective.\n\nAlso note that in the case of a compound event (a single event that contains\nmultiple logical events), this field should be populated with the value that\nbest captures the overall success or failure from the perspective of the event\nproducer.\n\nFurther note that not all events will have an associated outcome. For example,\nthis field is generally not populated for metric events, events with `event.type:info`,\nor any events for which an outcome does not make logical sense.', - example: 'success', - }, - { - name: 'provider', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Source of the event.\n\nEvent transports such as Syslog or the Windows Event Log typically mention\nthe source of an event. It can be the name of the software that generated\nthe event (e.g. Sysmon, httpd), or of a subsystem of the operating system\n(kernel, Microsoft-Windows-Security-Auditing).', - example: 'kernel', - }, - { - name: 'reference', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Reference URL linking to additional information about this event.\n\nThis URL links to a static definition of the this event. Alert events, indicated\nby `event.kind:alert`, are a common use case for this field.', - example: 'https://system.vendor.com/event/#0001234', - default_field: false, - }, - { - name: 'risk_score', - level: 'core', - type: 'float', - description: - "Risk score or priority of the event (e.g. security solutions).\nUse your system's original value here.", - }, - { - name: 'risk_score_norm', - level: 'extended', - type: 'float', - description: - 'Normalized risk score or priority of the event, on a scale of\n0 to 100.\n\nThis is mainly useful if you use more than one system that assigns risk scores,\nand you want to see a normalized value across all systems.', - }, - { - name: 'sequence', - level: 'extended', - type: 'long', - format: 'string', - description: - 'Sequence number of the event.\n\nThe sequence number is a value published by some event sources, to make the\nexact ordering of events unambiguous, regardless of the timestamp precision.', - }, - { - name: 'severity', - level: 'core', - type: 'long', - format: 'string', - description: - 'The numeric severity of the event according to your event source.\n\nWhat the different severity values mean can be different between sources and\nuse cases. It is up to the implementer to make sure severities are consistent\nacross events from the same source.\n\nThe Syslog severity belongs in `log.syslog.severity.code`. `event.severity`\nis meant to represent the severity according to the event source (e.g. firewall,\nIDS). If the event source does not publish its own severity, you may optionally\ncopy the `log.syslog.severity.code` to `event.severity`.', - example: 7, - }, - { - name: 'start', - level: 'extended', - type: 'date', - description: - 'event.start contains the date when the event started or when the\nactivity was first observed.', - }, - { - name: 'timezone', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'This field should be populated when the event timestamp does\nnot include timezone information already (e.g. default Syslog timestamps).\nIt is optional otherwise.\n\nAcceptable timezone formats are: a canonical ID (e.g. "Europe/Amsterdam"),\nabbreviated (e.g. "EST") or an HH:mm differential (e.g. "-05:00").', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'This is one of four ECS Categorization Fields, and indicates the\nthird level in the ECS category hierarchy.\n\n`event.type` represents a categorization "sub-bucket" that, when used along\nwith the `event.category` field values, enables filtering events down to a\nlevel appropriate for single visualization.\n\nThis field is an array. This will allow proper categorization of some events\nthat fall in multiple event types.', - }, - { - name: 'url', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'URL linking to an external system to continue investigation of\nthis event.\n\nThis URL links to another system where in-depth investigation of the specific\noccurence of this event can take place. Alert events, indicated by `event.kind:alert`,\nare a common use case for this field.', - example: 'https://mysystem.mydomain.com/alert/5271dedb-f5b0-4218-87f0-4ac4870a38fe', - default_field: false, - }, - ], - }, - { - name: 'file', - title: 'File', - group: 2, - description: - 'A file is defined as a set of information that has been created\non, or has existed on a filesystem.\n\nFile objects can be associated with host events, network events, and/or file\nevents (e.g., those produced by File Integrity Monitoring [FIM] products or\nservices). File fields provide details about the affected file associated with\nthe event or metric.', - type: 'group', - fields: [ - { - name: 'accessed', - level: 'extended', - type: 'date', - description: - 'Last time the file was accessed.\n\nNote that not all filesystems keep track of access time.', - }, - { - name: 'attributes', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Array of file attributes.\n\nAttributes names will vary by platform. Here is a non-exhaustive list of values\nthat are expected in this field: archive, compressed, directory, encrypted,\nexecute, hidden, read, readonly, system, write.', - example: '["readonly", "system"]', - default_field: false, - }, - { - name: 'code_signature.exists', - level: 'core', - type: 'boolean', - description: 'Boolean to capture if a signature is present.', - example: 'true', - default_field: false, - }, - { - name: 'code_signature.status', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Additional information about the certificate status.\n\nThis is useful for logging cryptographic errors with the certificate validity\nor trust status. Leave unpopulated if the validity or trust of the certificate\nwas unchecked.', - example: 'ERROR_UNTRUSTED_ROOT', - default_field: false, - }, - { - name: 'code_signature.subject_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Subject name of the code signer', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'code_signature.trusted', - level: 'extended', - type: 'boolean', - description: - 'Stores the trust status of the certificate chain.\n\nValidating the trust of the certificate chain may be complicated, and this\nfield should only be populated by tools that actively check the status.', - example: 'true', - default_field: false, - }, - { - name: 'code_signature.valid', - level: 'extended', - type: 'boolean', - description: - 'Boolean to capture if the digital signature is verified against\nthe binary content.\n\nLeave unpopulated if a certificate was unchecked.', - example: 'true', - default_field: false, - }, - { - name: 'created', - level: 'extended', - type: 'date', - description: - 'File creation time.\n\nNote that not all filesystems store the creation time.', - }, - { - name: 'ctime', - level: 'extended', - type: 'date', - description: - 'Last time the file attributes or metadata changed.\n\nNote that changes to the file content will update `mtime`. This implies `ctime`\nwill be adjusted at the same time, since `mtime` is an attribute of the file.', - }, - { - name: 'device', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Device that is the source of the file.', - example: 'sda', - }, - { - name: 'directory', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Directory where the file is located. It should include the drive\nletter, when appropriate.', - example: '/home/alice', - }, - { - name: 'drive_letter', - level: 'extended', - type: 'keyword', - ignore_above: 1, - description: - 'Drive letter where the file is located. This field is only relevant\non Windows.\n\nThe value should be uppercase, and not include the colon.', - example: 'C', - default_field: false, - }, - { - name: 'extension', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'File extension.', - example: 'png', - }, - { - name: 'gid', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Primary group ID (GID) of the file.', - example: '1001', - }, - { - name: 'group', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Primary group name of the file.', - example: 'alice', - }, - { - name: 'hash.md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'MD5 hash.', - }, - { - name: 'hash.sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA1 hash.', - }, - { - name: 'hash.sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA256 hash.', - }, - { - name: 'hash.sha512', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA512 hash.', - }, - { - name: 'inode', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Inode representing the file in the filesystem.', - example: '256383', - }, - { - name: 'mime_type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'MIME type should identify the format of the file or stream of bytes\nusing https://www.iana.org/assignments/media-types/media-types.xhtml[IANA\nofficial types], where possible. When more than one type is applicable, the\nmost specific type should be used.', - default_field: false, - }, - { - name: 'mode', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Mode of the file in octal representation.', - example: '0640', - }, - { - name: 'mtime', - level: 'extended', - type: 'date', - description: 'Last time the file content was modified.', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the file including the extension, without the directory.', - example: 'example.png', - }, - { - name: 'owner', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: "File owner's username.", - example: 'alice', - }, - { - name: 'path', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: - 'Full path to the file, including the file name. It should include\nthe drive letter, when appropriate.', - example: '/home/alice/example.png', - }, - { - name: 'pe.company', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal company name of the file, provided at compile-time.', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'pe.description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal description of the file, provided at compile-time.', - example: 'Paint', - default_field: false, - }, - { - name: 'pe.file_version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal version of the file, provided at compile-time.', - example: '6.3.9600.17415', - default_field: false, - }, - { - name: 'pe.original_file_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal name of the file, provided at compile-time.', - example: 'MSPAINT.EXE', - default_field: false, - }, - { - name: 'pe.product', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal product name of the file, provided at compile-time.', - example: 'Microsoft® Windows® Operating System', - default_field: false, - }, - { - name: 'size', - level: 'extended', - type: 'long', - description: 'File size in bytes.\n\nOnly relevant when `file.type` is "file".', - example: 16384, - }, - { - name: 'target_path', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Target path for symlinks.', - }, - { - name: 'type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'File type (file, dir, or symlink).', - example: 'file', - }, - { - name: 'uid', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The user ID (UID) or security identifier (SID) of the file owner.', - example: '1001', - }, - ], - }, - { - name: 'geo', - title: 'Geo', - group: 2, - description: - 'Geo fields can carry data about a specific location related to an\nevent.\n\nThis geolocation information can be derived from techniques such as Geo IP,\nor be user-supplied.', - type: 'group', - fields: [ - { - name: 'city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - ], - }, - { - name: 'group', - title: 'Group', - group: 2, - description: - 'The group fields are meant to represent groups that are relevant\nto the event.', - type: 'group', - fields: [ - { - name: 'domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - ], - }, - { - name: 'hash', - title: 'Hash', - group: 2, - description: - 'The hash fields represent different hash algorithms and their values.\n\nField names for common hashes (e.g. MD5, SHA1) are predefined. Add fields for\nother hashes by lowercasing the hash algorithm name and using underscore separators\nas appropriate (snake case, e.g. sha3_512).', - type: 'group', - fields: [ - { - name: 'md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'MD5 hash.', - }, - { - name: 'sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA1 hash.', - }, - { - name: 'sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA256 hash.', - }, - { - name: 'sha512', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA512 hash.', - }, - ], - }, - { - name: 'host', - title: 'Host', - group: 2, - description: - 'A host is defined as a general computing instance.\n\nECS host.* fields should be populated with details about the host on which the\nevent happened, or from which the measurement was taken. Host types include\nhardware, virtual machines, Docker containers, and Kubernetes nodes.', - type: 'group', - fields: [ - { - name: 'architecture', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system architecture.', - example: 'x86_64', - }, - { - name: 'domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the domain of which the host is a member.\n\nFor example, on Windows this could be the host Active Directory domain\nor NetBIOS domain name. For Linux this could be the domain of the host\nLDAP provider.', - example: 'CONTOSO', - default_field: false, - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'hostname', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Hostname of the host.\n\nIt normally contains what the `hostname` command returns on the host machine.', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique host id.\n\nAs hostname is not always unique, use values that are meaningful in your environment.\n\nExample: The current usage of `beat.name`.', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: 'Host ip addresses.', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Host mac addresses.', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the host.\n\nIt can contain what `hostname` returns on Unix systems, the fully qualified\ndomain name, or a name specified by the user. The sender decides which value\nto use.', - }, - { - name: 'os.family', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'OS family (such as redhat, debian, freebsd, windows).', - example: 'debian', - }, - { - name: 'os.full', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, including the version or code name.', - example: 'Mac OS Mojave', - }, - { - name: 'os.kernel', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system kernel version as a raw string.', - example: '4.4.0-112-generic', - }, - { - name: 'os.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, without the version.', - example: 'Mac OS X', - }, - { - name: 'os.platform', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'os.version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system version as a raw string.', - example: '10.14.1', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Type of host.\n\nFor Cloud providers this can be the machine type like `t2.medium`. If vm,\nthis could be the container, for example, or other information meaningful\nin your environment.', - }, - { - name: 'uptime', - level: 'extended', - type: 'long', - description: 'Seconds the host has been up.', - example: 1325, - }, - { - name: 'user.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the user is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.email', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'User email address.', - }, - { - name: 'user.full_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'user.group.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.group.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'user.group.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - { - name: 'user.hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'user.id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifiers of the user.', - }, - { - name: 'user.name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'http', - title: 'HTTP', - group: 2, - description: - 'Fields related to HTTP activity. Use the `url` field set to store\nthe url of the request.', - type: 'group', - fields: [ - { - name: 'request.body.bytes', - level: 'extended', - type: 'long', - format: 'bytes', - description: 'Size in bytes of the request body.', - example: 887, - }, - { - name: 'request.body.content', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'The full HTTP request body.', - example: 'Hello world', - }, - { - name: 'request.bytes', - level: 'extended', - type: 'long', - format: 'bytes', - description: 'Total size in bytes of the request (body and headers).', - example: 1437, - }, - { - name: 'request.method', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'HTTP request method.\n\nThe field value must be normalized to lowercase for querying. See the documentation\nsection "Implementing ECS".', - example: 'get, post, put', - }, - { - name: 'request.referrer', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Referrer for this HTTP request.', - example: 'https://blog.example.com/', - }, - { - name: 'response.body.bytes', - level: 'extended', - type: 'long', - format: 'bytes', - description: 'Size in bytes of the response body.', - example: 887, - }, - { - name: 'response.body.content', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'The full HTTP response body.', - example: 'Hello world', - }, - { - name: 'response.bytes', - level: 'extended', - type: 'long', - format: 'bytes', - description: 'Total size in bytes of the response (body and headers).', - example: 1437, - }, - { - name: 'response.status_code', - level: 'extended', - type: 'long', - format: 'string', - description: 'HTTP response status code.', - example: 404, - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'HTTP version.', - example: 1.1, - }, - ], - }, - { - name: 'interface', - title: 'Interface', - group: 2, - description: - 'The interface fields are used to record ingress and egress interface\ninformation when reported by an observer (e.g. firewall, router, load balancer)\nin the context of the observer handling a network connection. In the case of\na single observer interface (e.g. network sensor on a span port) only the observer.ingress\ninformation should be populated.', - type: 'group', - fields: [ - { - name: 'alias', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Interface alias as reported by the system, typically used in firewall\nimplementations for e.g. inside, outside, or dmz logical interface naming.', - example: 'outside', - default_field: false, - }, - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Interface ID as reported by an observer (typically SNMP interface\nID).', - example: 10, - default_field: false, - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Interface name as reported by the system.', - example: 'eth0', - default_field: false, - }, - ], - }, - { - name: 'log', - title: 'Log', - group: 2, - description: - 'Details about the event logging mechanism or logging transport.\n\nThe log.* fields are typically populated with details about the logging mechanism\nused to create and/or transport the event. For example, syslog details belong\nunder `log.syslog.*`.\n\nThe details specific to your event source are typically not logged under `log.*`,\nbut rather in `event.*` or in other ECS fields.', - type: 'group', - fields: [ - { - name: 'level', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Original log level of the log event.\n\nIf the source of the event provides a log level or textual severity, this\nis the one that goes in `log.level`. If your source does not specify one,\nyou may put your event transport severity here (e.g. Syslog severity).\n\nSome examples are `warn`, `err`, `i`, `informational`.', - example: 'error', - }, - { - name: 'logger', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'The name of the logger inside an application. This is usually the\nname of the class which initialized the logger, or can be a custom name.', - example: 'org.elasticsearch.bootstrap.Bootstrap', - }, - { - name: 'origin.file.line', - level: 'extended', - type: 'integer', - description: - 'The line number of the file containing the source code which originated\nthe log event.', - example: 42, - }, - { - name: 'origin.file.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The name of the file containing the source code which originated\nthe log event. Note that this is not the name of the log file.', - example: 'Bootstrap.java', - }, - { - name: 'origin.function', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The name of the function or method which originated the log event.', - example: 'init', - }, - { - name: 'original', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'This is the original log message and contains the full log message\nbefore splitting it up in multiple parts.\n\nIn contrast to the `message` field which can contain an extracted part of\nthe log message, this field contains the original, full log message. It can\nhave already some modifications applied like encoding or new lines removed\nto clean up the log message.\n\nThis field is not indexed and doc_values are disabled so it cannot be queried\nbut the value can be retrieved from `_source`.', - example: 'Sep 19 08:26:10 localhost My log', - }, - { - name: 'syslog', - level: 'extended', - type: 'object', - object_type: 'keyword', - description: - 'The Syslog metadata of the event, if the event was transmitted\nvia Syslog. Please see RFCs 5424 or 3164.', - }, - { - name: 'syslog.facility.code', - level: 'extended', - type: 'long', - format: 'string', - description: - 'The Syslog numeric facility of the log event, if available.\n\nAccording to RFCs 5424 and 3164, this value should be an integer between 0\nand 23.', - example: 23, - }, - { - name: 'syslog.facility.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The Syslog text-based facility of the log event, if available.', - example: 'local7', - }, - { - name: 'syslog.priority', - level: 'extended', - type: 'long', - format: 'string', - description: - 'Syslog numeric priority of the event, if available.\n\nAccording to RFCs 5424 and 3164, the priority is 8 * facility + severity.\nThis number is therefore expected to contain a value between 0 and 191.', - example: 135, - }, - { - name: 'syslog.severity.code', - level: 'extended', - type: 'long', - description: - 'The Syslog numeric severity of the log event, if available.\n\nIf the event source publishing via Syslog provides a different numeric severity\nvalue (e.g. firewall, IDS), your source numeric severity should go to `event.severity`.\nIf the event source does not specify a distinct severity, you can optionally\ncopy the Syslog severity to `event.severity`.', - example: 3, - }, - { - name: 'syslog.severity.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The Syslog numeric severity of the log event, if available.\n\nIf the event source publishing via Syslog provides a different severity value\n(e.g. firewall, IDS), your source text severity should go to `log.level`.\nIf the event source does not specify a distinct severity, you can optionally\ncopy the Syslog severity to `log.level`.', - example: 'Error', - }, - ], - }, - { - name: 'network', - title: 'Network', - group: 2, - description: - 'The network is defined as the communication path over which a host\nor network event happens.\n\nThe network.* fields should be populated with details about the network activity\nassociated with an event.', - type: 'group', - fields: [ - { - name: 'application', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A name given to an application level protocol. This can be arbitrarily\nassigned for things like microservices, but also apply to things like skype,\nicq, facebook, twitter. This would be used in situations where the vendor\nor service can be decoded such as from the source/dest IP owners, ports, or\nwire format.\n\nThe field value must be normalized to lowercase for querying. See the documentation\nsection "Implementing ECS".', - example: 'aim', - }, - { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - description: - 'Total bytes transferred in both directions.\n\nIf `source.bytes` and `destination.bytes` are known, `network.bytes` is their\nsum.', - example: 368, - }, - { - name: 'community_id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A hash of source and destination IPs and ports, as well as the\nprotocol used in a communication. This is a tool-agnostic standard to identify\nflows.\n\nLearn more at https://github.com/corelight/community-id-spec.', - example: '1:hO+sN4H+MG5MY/8hIrXPqc4ZQz0=', - }, - { - name: 'direction', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - "Direction of the network traffic.\nRecommended values are:\n * inbound\n * outbound\n * internal\n * external\n * unknown\n\nWhen mapping events from a host-based monitoring context, populate this field from the host's point of view.\nWhen mapping events from a network or perimeter-based monitoring context, populate this field from the point of view of your network perimeter.", - example: 'inbound', - }, - { - name: 'forwarded_ip', - level: 'core', - type: 'ip', - description: 'Host IP address when the source IP address is the proxy.', - example: '192.1.1.2', - }, - { - name: 'iana_number', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'IANA Protocol Number (https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml).\nStandardized list of protocols. This aligns well with NetFlow and sFlow related\nlogs which use the IANA Protocol Number.', - example: 6, - }, - { - name: 'inner', - level: 'extended', - type: 'object', - object_type: 'keyword', - description: - 'Network.inner fields are added in addition to network.vlan fields\nto describe the innermost VLAN when q-in-q VLAN tagging is present. Allowed\nfields include vlan.id and vlan.name. Inner vlan fields are typically used\nwhen sending traffic with multiple 802.1q encapsulations to a network sensor\n(e.g. Zeek, Wireshark.)', - default_field: false, - }, - { - name: 'inner.vlan.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'VLAN ID as reported by the observer.', - example: 10, - default_field: false, - }, - { - name: 'inner.vlan.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Optional VLAN name as reported by the observer.', - example: 'outside', - default_field: false, - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name given by operators to sections of their network.', - example: 'Guest Wifi', - }, - { - name: 'packets', - level: 'core', - type: 'long', - description: - 'Total packets transferred in both directions.\n\nIf `source.packets` and `destination.packets` are known, `network.packets`\nis their sum.', - example: 24, - }, - { - name: 'protocol', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'L7 Network protocol name. ex. http, lumberjack, transport protocol.\n\nThe field value must be normalized to lowercase for querying. See the documentation\nsection "Implementing ECS".', - example: 'http', - }, - { - name: 'transport', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Same as network.iana_number, but instead using the Keyword name\nof the transport layer (udp, tcp, ipv6-icmp, etc.)\n\nThe field value must be normalized to lowercase for querying. See the documentation\nsection "Implementing ECS".', - example: 'tcp', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'In the OSI Model this would be the Network Layer. ipv4, ipv6,\nipsec, pim, etc\n\nThe field value must be normalized to lowercase for querying. See the documentation\nsection "Implementing ECS".', - example: 'ipv4', - }, - { - name: 'vlan.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'VLAN ID as reported by the observer.', - example: 10, - default_field: false, - }, - { - name: 'vlan.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Optional VLAN name as reported by the observer.', - example: 'outside', - default_field: false, - }, - ], - }, - { - name: 'observer', - title: 'Observer', - group: 2, - description: - 'An observer is defined as a special network, security, or application\ndevice used to detect, observe, or create network, security, or application-related\nevents and metrics.\n\nThis could be a custom hardware appliance or a server that has been configured\nto run special network, security, or application software. Examples include\nfirewalls, web proxies, intrusion detection/prevention systems, network monitoring\nsensors, web application firewalls, data loss prevention systems, and APM servers.\nThe observer.* fields shall be populated with details of the system, if any,\nthat detects, observes and/or creates a network, security, or application event\nor metric. Message queues and ETL components used in processing events or metrics\nare not considered observers in ECS.', - type: 'group', - fields: [ - { - name: 'egress', - level: 'extended', - type: 'object', - object_type: 'keyword', - description: - 'Observer.egress holds information like interface number and name,\nvlan, and zone information to classify egress traffic. Single armed monitoring\nsuch as a network sensor on a span port should only use observer.ingress\nto categorize traffic.', - default_field: false, - }, - { - name: 'egress.interface.alias', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Interface alias as reported by the system, typically used in firewall\nimplementations for e.g. inside, outside, or dmz logical interface naming.', - example: 'outside', - default_field: false, - }, - { - name: 'egress.interface.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Interface ID as reported by an observer (typically SNMP interface\nID).', - example: 10, - default_field: false, - }, - { - name: 'egress.interface.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Interface name as reported by the system.', - example: 'eth0', - default_field: false, - }, - { - name: 'egress.vlan.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'VLAN ID as reported by the observer.', - example: 10, - default_field: false, - }, - { - name: 'egress.vlan.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Optional VLAN name as reported by the observer.', - example: 'outside', - default_field: false, - }, - { - name: 'egress.zone', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Network zone of outbound traffic as reported by the observer to\ncategorize the destination area of egress traffic, e.g. Internal, External,\nDMZ, HR, Legal, etc.', - example: 'Public_Internet', - default_field: false, - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'hostname', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Hostname of the observer.', - }, - { - name: 'ingress', - level: 'extended', - type: 'object', - object_type: 'keyword', - description: - 'Observer.ingress holds information like interface number and name,\nvlan, and zone information to classify ingress traffic. Single armed monitoring\nsuch as a network sensor on a span port should only use observer.ingress\nto categorize traffic.', - default_field: false, - }, - { - name: 'ingress.interface.alias', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Interface alias as reported by the system, typically used in firewall\nimplementations for e.g. inside, outside, or dmz logical interface naming.', - example: 'outside', - default_field: false, - }, - { - name: 'ingress.interface.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Interface ID as reported by an observer (typically SNMP interface\nID).', - example: 10, - default_field: false, - }, - { - name: 'ingress.interface.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Interface name as reported by the system.', - example: 'eth0', - default_field: false, - }, - { - name: 'ingress.vlan.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'VLAN ID as reported by the observer.', - example: 10, - default_field: false, - }, - { - name: 'ingress.vlan.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Optional VLAN name as reported by the observer.', - example: 'outside', - default_field: false, - }, - { - name: 'ingress.zone', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Network zone of incoming traffic as reported by the observer to\ncategorize the source area of ingress traffic. e.g. internal, External, DMZ,\nHR, Legal, etc.', - example: 'DMZ', - default_field: false, - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: 'IP addresses of the observer.', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'MAC addresses of the observer', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Custom name of the observer.\n\nThis is a name that can be given to an observer. This can be helpful for example\nif multiple firewalls of the same model are used in an organization.\n\nIf no custom name is needed, the field can be left empty.', - example: '1_proxySG', - }, - { - name: 'os.family', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'OS family (such as redhat, debian, freebsd, windows).', - example: 'debian', - }, - { - name: 'os.full', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, including the version or code name.', - example: 'Mac OS Mojave', - }, - { - name: 'os.kernel', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system kernel version as a raw string.', - example: '4.4.0-112-generic', - }, - { - name: 'os.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, without the version.', - example: 'Mac OS X', - }, - { - name: 'os.platform', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'os.version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system version as a raw string.', - example: '10.14.1', - }, - { - name: 'product', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The product name of the observer.', - example: 's200', - }, - { - name: 'serial_number', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Observer serial number.', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'The type of the observer the data is coming from.\n\nThere is no predefined list of observer types. Some examples are `forwarder`,\n`firewall`, `ids`, `ips`, `proxy`, `poller`, `sensor`, `APM server`.', - example: 'firewall', - }, - { - name: 'vendor', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Vendor name of the observer.', - example: 'Symantec', - }, - { - name: 'version', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Observer version.', - }, - ], - }, - { - name: 'organization', - title: 'Organization', - group: 2, - description: - 'The organization fields enrich data with information about the company\nor entity the data is associated with.\n\nThese fields help you arrange or filter data stored in an index by one or multiple\norganizations.', - type: 'group', - fields: [ - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the organization.', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Organization name.', - }, - ], - }, - { - name: 'os', - title: 'Operating System', - group: 2, - description: 'The OS fields contain information about the operating system.', - type: 'group', - fields: [ - { - name: 'family', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'OS family (such as redhat, debian, freebsd, windows).', - example: 'debian', - }, - { - name: 'full', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, including the version or code name.', - example: 'Mac OS Mojave', - }, - { - name: 'kernel', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system kernel version as a raw string.', - example: '4.4.0-112-generic', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, without the version.', - example: 'Mac OS X', - }, - { - name: 'platform', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system version as a raw string.', - example: '10.14.1', - }, - ], - }, - { - name: 'package', - title: 'Package', - group: 2, - description: - 'These fields contain information about an installed software package.\nIt contains general information about a package, such as name, version or size.\nIt also contains installation details, such as time or location.', - type: 'group', - fields: [ - { - name: 'architecture', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Package architecture.', - example: 'x86_64', - }, - { - name: 'build_version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Additional information about the build version of the installed\npackage.\n\nFor example use the commit SHA of a non-released package.', - example: '36f4f7e89dd61b0988b12ee000b98966867710cd', - default_field: false, - }, - { - name: 'checksum', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Checksum of the installed package for verification.', - example: '68b329da9893e34099c7d8ad5cb9c940', - }, - { - name: 'description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Description of the package.', - example: - 'Open source programming language to build simple/reliable/efficient\nsoftware.', - }, - { - name: 'install_scope', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Indicating how the package was installed, e.g. user-local, global.', - example: 'global', - }, - { - name: 'installed', - level: 'extended', - type: 'date', - description: 'Time when package was installed.', - }, - { - name: 'license', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'License under which the package was released.\n\nUse a short name, e.g. the license identifier from SPDX License List where\npossible (https://spdx.org/licenses/).', - example: 'Apache License 2.0', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Package name', - example: 'go', - }, - { - name: 'path', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Path where the package is installed.', - example: '/usr/local/Cellar/go/1.12.9/', - }, - { - name: 'reference', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Home page or reference URL of the software in this package, if\navailable.', - example: 'https://golang.org', - default_field: false, - }, - { - name: 'size', - level: 'extended', - type: 'long', - format: 'string', - description: 'Package size in bytes.', - example: 62231, - }, - { - name: 'type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Type of package.\n\nThis should contain the package file type, rather than the package manager\nname. Examples: rpm, dpkg, brew, npm, gem, nupkg, jar.', - example: 'rpm', - default_field: false, - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Package version', - example: '1.12.9', - }, - ], - }, - { - name: 'pe', - title: 'PE Header', - group: 2, - description: 'These fields contain Windows Portable Executable (PE) metadata.', - type: 'group', - fields: [ - { - name: 'company', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal company name of the file, provided at compile-time.', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal description of the file, provided at compile-time.', - example: 'Paint', - default_field: false, - }, - { - name: 'file_version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal version of the file, provided at compile-time.', - example: '6.3.9600.17415', - default_field: false, - }, - { - name: 'original_file_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal name of the file, provided at compile-time.', - example: 'MSPAINT.EXE', - default_field: false, - }, - { - name: 'product', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal product name of the file, provided at compile-time.', - example: 'Microsoft® Windows® Operating System', - default_field: false, - }, - ], - }, - { - name: 'process', - title: 'Process', - group: 2, - description: - 'These fields contain information about a process.\n\nThese fields can help you correlate metrics information with a process id/name\nfrom a log message. The `process.pid` often stays in the metric itself and\nis copied to the global field for correlation.', - type: 'group', - fields: [ - { - name: 'args', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.', - example: ['/usr/bin/ssh', '-l', 'user', '10.0.0.16'], - }, - { - name: 'args_count', - level: 'extended', - type: 'long', - description: - 'Length of the process.args array.\n\nThis field can be useful for querying or performing bucket analysis on how\nmany arguments were provided to start a process. More arguments may be an\nindication of suspicious activity.', - example: 4, - default_field: false, - }, - { - name: 'code_signature.exists', - level: 'core', - type: 'boolean', - description: 'Boolean to capture if a signature is present.', - example: 'true', - default_field: false, - }, - { - name: 'code_signature.status', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Additional information about the certificate status.\n\nThis is useful for logging cryptographic errors with the certificate validity\nor trust status. Leave unpopulated if the validity or trust of the certificate\nwas unchecked.', - example: 'ERROR_UNTRUSTED_ROOT', - default_field: false, - }, - { - name: 'code_signature.subject_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Subject name of the code signer', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'code_signature.trusted', - level: 'extended', - type: 'boolean', - description: - 'Stores the trust status of the certificate chain.\n\nValidating the trust of the certificate chain may be complicated, and this\nfield should only be populated by tools that actively check the status.', - example: 'true', - default_field: false, - }, - { - name: 'code_signature.valid', - level: 'extended', - type: 'boolean', - description: - 'Boolean to capture if the digital signature is verified against\nthe binary content.\n\nLeave unpopulated if a certificate was unchecked.', - example: 'true', - default_field: false, - }, - { - name: 'command_line', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: - 'Full command line that started the process, including the absolute\npath to the executable, and all arguments.\n\nSome arguments may be filtered to protect sensitive information.', - example: '/usr/bin/ssh -l user 10.0.0.16', - default_field: false, - }, - { - name: 'entity_id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique identifier for the process.\n\nThe implementation of this is specified by the data source, but some examples\nof what could be used here are a process-generated UUID, Sysmon Process GUIDs,\nor a hash of some uniquely identifying components of a process.\n\nConstructing a globally unique identifier is a common practice to mitigate\nPID reuse as well as to identify a specific process over time, across multiple\nmonitored hosts.', - example: 'c2c455d9f99375d', - default_field: false, - }, - { - name: 'executable', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Absolute path to the process executable.', - example: '/usr/bin/ssh', - }, - { - name: 'exit_code', - level: 'extended', - type: 'long', - description: - 'The exit code of the process, if this is a termination event.\n\nThe field should be absent if there is no exit code for the event (e.g. process\nstart).', - example: 137, - default_field: false, - }, - { - name: 'hash.md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'MD5 hash.', - }, - { - name: 'hash.sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA1 hash.', - }, - { - name: 'hash.sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA256 hash.', - }, - { - name: 'hash.sha512', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA512 hash.', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Process name.\n\nSometimes called program name or similar.', - example: 'ssh', - }, - { - name: 'parent.args', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Array of process arguments.\n\nMay be filtered to protect sensitive information.', - example: ['ssh', '-l', 'user', '10.0.0.16'], - default_field: false, - }, - { - name: 'parent.args_count', - level: 'extended', - type: 'long', - description: - 'Length of the process.args array.\n\nThis field can be useful for querying or performing bucket analysis on how\nmany arguments were provided to start a process. More arguments may be an\nindication of suspicious activity.', - example: 4, - default_field: false, - }, - { - name: 'parent.code_signature.exists', - level: 'core', - type: 'boolean', - description: 'Boolean to capture if a signature is present.', - example: 'true', - default_field: false, - }, - { - name: 'parent.code_signature.status', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Additional information about the certificate status.\n\nThis is useful for logging cryptographic errors with the certificate validity\nor trust status. Leave unpopulated if the validity or trust of the certificate\nwas unchecked.', - example: 'ERROR_UNTRUSTED_ROOT', - default_field: false, - }, - { - name: 'parent.code_signature.subject_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Subject name of the code signer', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'parent.code_signature.trusted', - level: 'extended', - type: 'boolean', - description: - 'Stores the trust status of the certificate chain.\n\nValidating the trust of the certificate chain may be complicated, and this\nfield should only be populated by tools that actively check the status.', - example: 'true', - default_field: false, - }, - { - name: 'parent.code_signature.valid', - level: 'extended', - type: 'boolean', - description: - 'Boolean to capture if the digital signature is verified against\nthe binary content.\n\nLeave unpopulated if a certificate was unchecked.', - example: 'true', - default_field: false, - }, - { - name: 'parent.command_line', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: - 'Full command line that started the process, including the absolute\npath to the executable, and all arguments.\n\nSome arguments may be filtered to protect sensitive information.', - example: '/usr/bin/ssh -l user 10.0.0.16', - default_field: false, - }, - { - name: 'parent.entity_id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique identifier for the process.\n\nThe implementation of this is specified by the data source, but some examples\nof what could be used here are a process-generated UUID, Sysmon Process GUIDs,\nor a hash of some uniquely identifying components of a process.\n\nConstructing a globally unique identifier is a common practice to mitigate\nPID reuse as well as to identify a specific process over time, across multiple\nmonitored hosts.', - example: 'c2c455d9f99375d', - default_field: false, - }, - { - name: 'parent.executable', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: 'Absolute path to the process executable.', - example: '/usr/bin/ssh', - default_field: false, - }, - { - name: 'parent.exit_code', - level: 'extended', - type: 'long', - description: - 'The exit code of the process, if this is a termination event.\n\nThe field should be absent if there is no exit code for the event (e.g. process\nstart).', - example: 137, - default_field: false, - }, - { - name: 'parent.hash.md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'MD5 hash.', - default_field: false, - }, - { - name: 'parent.hash.sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA1 hash.', - default_field: false, - }, - { - name: 'parent.hash.sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA256 hash.', - default_field: false, - }, - { - name: 'parent.hash.sha512', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA512 hash.', - default_field: false, - }, - { - name: 'parent.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: 'Process name.\n\nSometimes called program name or similar.', - example: 'ssh', - default_field: false, - }, - { - name: 'parent.pgid', - level: 'extended', - type: 'long', - format: 'string', - description: 'Identifier of the group of processes the process belongs to.', - default_field: false, - }, - { - name: 'parent.pid', - level: 'core', - type: 'long', - format: 'string', - description: 'Process id.', - example: 4242, - default_field: false, - }, - { - name: 'parent.ppid', - level: 'extended', - type: 'long', - format: 'string', - description: "Parent process' pid.", - example: 4241, - default_field: false, - }, - { - name: 'parent.start', - level: 'extended', - type: 'date', - description: 'The time the process started.', - example: '2016-05-23T08:05:34.853Z', - default_field: false, - }, - { - name: 'parent.thread.id', - level: 'extended', - type: 'long', - format: 'string', - description: 'Thread ID.', - example: 4242, - default_field: false, - }, - { - name: 'parent.thread.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Thread name.', - example: 'thread-0', - default_field: false, - }, - { - name: 'parent.title', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: - 'Process title.\n\nThe proctitle, some times the same as process name. Can also be different:\nfor example a browser setting its title to the web page currently opened.', - default_field: false, - }, - { - name: 'parent.uptime', - level: 'extended', - type: 'long', - description: 'Seconds the process has been up.', - example: 1325, - default_field: false, - }, - { - name: 'parent.working_directory', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: 'The working directory of the process.', - example: '/home/alice', - default_field: false, - }, - { - name: 'pe.company', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal company name of the file, provided at compile-time.', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'pe.description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal description of the file, provided at compile-time.', - example: 'Paint', - default_field: false, - }, - { - name: 'pe.file_version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal version of the file, provided at compile-time.', - example: '6.3.9600.17415', - default_field: false, - }, - { - name: 'pe.original_file_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal name of the file, provided at compile-time.', - example: 'MSPAINT.EXE', - default_field: false, - }, - { - name: 'pe.product', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal product name of the file, provided at compile-time.', - example: 'Microsoft® Windows® Operating System', - default_field: false, - }, - { - name: 'pgid', - level: 'extended', - type: 'long', - format: 'string', - description: 'Identifier of the group of processes the process belongs to.', - }, - { - name: 'pid', - level: 'core', - type: 'long', - format: 'string', - description: 'Process id.', - example: 4242, - }, - { - name: 'ppid', - level: 'extended', - type: 'long', - format: 'string', - description: "Parent process' pid.", - example: 4241, - }, - { - name: 'start', - level: 'extended', - type: 'date', - description: 'The time the process started.', - example: '2016-05-23T08:05:34.853Z', - }, - { - name: 'thread.id', - level: 'extended', - type: 'long', - format: 'string', - description: 'Thread ID.', - example: 4242, - }, - { - name: 'thread.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Thread name.', - example: 'thread-0', - }, - { - name: 'title', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: - 'Process title.\n\nThe proctitle, some times the same as process name. Can also be different:\nfor example a browser setting its title to the web page currently opened.', - }, - { - name: 'uptime', - level: 'extended', - type: 'long', - description: 'Seconds the process has been up.', - example: 1325, - }, - { - name: 'working_directory', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'The working directory of the process.', - example: '/home/alice', - }, - ], - }, - { - name: 'registry', - title: 'Registry', - group: 2, - description: 'Fields related to Windows Registry operations.', - type: 'group', - fields: [ - { - name: 'data.bytes', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Original bytes written with base64 encoding.\n\nFor Windows registry operations, such as SetValueEx and RegQueryValueEx, this\ncorresponds to the data pointed by `lp_data`. This is optional but provides\nbetter recoverability and should be populated for REG_BINARY encoded values.', - example: 'ZQBuAC0AVQBTAAAAZQBuAAAAAAA=', - default_field: false, - }, - { - name: 'data.strings', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Content when writing string types.\n\nPopulated as an array when writing string data to the registry. For single\nstring registry types (REG_SZ, REG_EXPAND_SZ), this should be an array with\none string. For sequences of string with REG_MULTI_SZ, this array will be\nvariable length. For numeric data, such as REG_DWORD and REG_QWORD, this should\nbe populated with the decimal representation (e.g `"1"`).', - example: '["C:\\rta\\red_ttp\\bin\\myapp.exe"]', - default_field: false, - }, - { - name: 'data.type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Standard registry type for encoding contents', - example: 'REG_SZ', - default_field: false, - }, - { - name: 'hive', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Abbreviated name for the hive.', - example: 'HKLM', - default_field: false, - }, - { - name: 'key', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Hive-relative path of keys.', - example: - 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\winword.exe', - default_field: false, - }, - { - name: 'path', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Full path, including hive, key and value', - example: - 'HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution\nOptions\\winword.exe\\Debugger', - default_field: false, - }, - { - name: 'value', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the value written.', - example: 'Debugger', - default_field: false, - }, - ], - }, - { - name: 'related', - title: 'Related', - group: 2, - description: - 'This field set is meant to facilitate pivoting around a piece of\ndata.\n\nSome pieces of information can be seen in many places in an ECS event. To facilitate\nsearching for them, store an array of all seen values to their corresponding\nfield in `related.`.\n\nA concrete example is IP addresses, which can be under host, observer, source,\ndestination, client, server, and network.forwarded_ip. If you append all IPs\nto `related.ip`, you can then search for a given IP trivially, no matter where\nit appeared, by querying `related.ip:192.0.2.15`.', - type: 'group', - fields: [ - { - name: 'hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - "All the hashes seen on your event. Populating this field, then\nusing it to search for hashes can help in situations where you're unsure what\nthe hash algorithm is (and therefore which key name to search).", - default_field: false, - }, - { - name: 'ip', - level: 'extended', - type: 'ip', - description: 'All of the IPs seen on your event.', - }, - { - name: 'user', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'All the user names seen on your event.', - default_field: false, - }, - ], - }, - { - name: 'rule', - title: 'Rule', - group: 2, - description: - 'Rule fields are used to capture the specifics of any observer or\nagent rules that generate alerts or other notable events.\n\nExamples of data sources that would populate the rule fields include: network\nadmission control platforms, network or host IDS/IPS, network firewalls, web\napplication firewalls, url filters, endpoint detection and response (EDR) systems,\netc.', - type: 'group', - fields: [ - { - name: 'author', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name, organization, or pseudonym of the author or authors who created\nthe rule used to generate this event.', - example: ['Star-Lord'], - default_field: false, - }, - { - name: 'category', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A categorization value keyword used by the entity using the rule\nfor detection of this event.', - example: 'Attempted Information Leak', - default_field: false, - }, - { - name: 'description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The description of the rule generating the event.', - example: 'Block requests to public DNS over HTTPS / TLS protocols', - default_field: false, - }, - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A rule ID that is unique within the scope of an agent, observer,\nor other entity using the rule for detection of this event.', - example: 101, - default_field: false, - }, - { - name: 'license', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the license under which the rule used to generate this\nevent is made available.', - example: 'Apache 2.0', - default_field: false, - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The name of the rule or signature generating the event.', - example: 'BLOCK_DNS_over_TLS', - default_field: false, - }, - { - name: 'reference', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Reference URL to additional information about the rule used to\ngenerate this event.\n\nThe URL can point to the vendor documentation about the rule. If that is\nnot available, it can also be a link to a more general page describing this\ntype of alert.', - example: 'https://en.wikipedia.org/wiki/DNS_over_TLS', - default_field: false, - }, - { - name: 'ruleset', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the ruleset, policy, group, or parent category in which\nthe rule used to generate this event is a member.', - example: 'Standard_Protocol_Filters', - default_field: false, - }, - { - name: 'uuid', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A rule ID that is unique within the scope of a set or group of\nagents, observers, or other entities using the rule for detection of this\nevent.', - example: 1100110011, - default_field: false, - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The version / revision of the rule being used for analysis.', - example: 1.1, - default_field: false, - }, - ], - }, - { - name: 'server', - title: 'Server', - group: 2, - description: - 'A Server is defined as the responder in a network connection for\nevents regarding sessions, connections, or bidirectional flow records.\n\nFor TCP events, the server is the receiver of the initial SYN packet(s) of the\nTCP connection. For other protocols, the server is generally the responder in\nthe network transaction. Some systems actually use the term "responder" to refer\nthe server in TCP connections. The server fields describe details about the\nsystem acting as the server in the network event. Server fields are usually\npopulated in conjunction with client fields. Server fields are generally not\npopulated for packet-level events.\n\nClient / server representations can add semantic context to an exchange, which\nis helpful to visualize the data in certain situations. If your context falls\nin that category, you should still ensure that source and destination are filled\nappropriately.', - type: 'group', - fields: [ - { - name: 'address', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Some event server addresses are defined ambiguously. The event\nwill sometimes list an IP, a domain or a unix socket. You should always store\nthe raw address in the `.address` field.\n\nThen it should be duplicated to `.ip` or `.domain`, depending on which one\nit is.', - }, - { - name: 'as.number', - level: 'extended', - type: 'long', - description: - 'Unique number allocated to the autonomous system. The autonomous\nsystem number (ASN) uniquely identifies each network on the Internet.', - example: 15169, - }, - { - name: 'as.organization.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Organization name.', - example: 'Google LLC', - }, - { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - description: 'Bytes sent from the server to the client.', - example: 184, - }, - { - name: 'domain', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Server domain.', - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: - 'IP address of the server.\n\nCan be one or multiple IPv4 or IPv6 addresses.', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'MAC address of the server.', - }, - { - name: 'nat.ip', - level: 'extended', - type: 'ip', - description: - 'Translated ip of destination based NAT sessions (e.g. internet\nto private DMZ)\n\nTypically used with load balancers, firewalls, or routers.', - }, - { - name: 'nat.port', - level: 'extended', - type: 'long', - format: 'string', - description: - 'Translated port of destination based NAT sessions (e.g. internet\nto private DMZ)\n\nTypically used with load balancers, firewalls, or routers.', - }, - { - name: 'packets', - level: 'core', - type: 'long', - description: 'Packets sent from the server to the client.', - example: 12, - }, - { - name: 'port', - level: 'core', - type: 'long', - format: 'string', - description: 'Port of the server.', - }, - { - name: 'registered_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The highest registered server domain, stripped of the subdomain.\n\nFor example, the registered domain for "foo.google.com" is "google.com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last two labels will not work well for TLDs such as "co.uk".', - example: 'google.com', - }, - { - name: 'top_level_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The effective top level domain (eTLD), also known as the domain\nsuffix, is the last part of the domain name. For example, the top level domain\nfor google.com is "com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last label will not work well for effective TLDs such as "co.uk".', - example: 'co.uk', - }, - { - name: 'user.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the user is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.email', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'User email address.', - }, - { - name: 'user.full_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'user.group.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.group.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'user.group.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - { - name: 'user.hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'user.id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifiers of the user.', - }, - { - name: 'user.name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'service', - title: 'Service', - group: 2, - description: - 'The service fields describe the service for or from which the data\nwas collected.\n\nThese fields help you find and correlate logs for a specific service and version.', - type: 'group', - fields: [ - { - name: 'ephemeral_id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Ephemeral identifier of this service (if one exists).\n\nThis id normally changes across restarts, but `service.id` does not.', - example: '8a4f500f', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique identifier of the running service. If the service is comprised\nof many nodes, the `service.id` should be the same for all nodes.\n\nThis id should uniquely identify the service. This makes it possible to correlate\nlogs and metrics for one specific service, no matter which particular node\nemitted the event.\n\nNote that if you need to see the events from one specific host of the service,\nyou should filter on that `host.name` or `host.id` instead.', - example: 'd37e5ebfe0ae6c4972dbe9f0174a1637bb8247f6', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the service data is collected from.\n\nThe name of the service is normally user given. This allows for distributed\nservices that run on multiple hosts to correlate the related instances based\non the name.\n\nIn the case of Elasticsearch the `service.name` could contain the cluster\nname. For Beats the `service.name` is by default a copy of the `service.type`\nfield if no name is specified.', - example: 'elasticsearch-metrics', - }, - { - name: 'node.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of a service node.\n\nThis allows for two nodes of the same service running on the same host to\nbe differentiated. Therefore, `service.node.name` should typically be unique\nacross nodes of a given service.\n\nIn the case of Elasticsearch, the `service.node.name` could contain the unique\nnode name within the Elasticsearch cluster. In cases where the service does not\nhave the concept of a node name, the host name or container name can be used\nto distinguish running instances that make up this service. If those do not\nprovide uniqueness (e.g. multiple instances of the service running on the\nsame host) - the node name can be manually set.', - example: 'instance-0000000016', - }, - { - name: 'state', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Current state of the service.', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'The type of the service data is collected from.\n\nThe type can be used to group and correlate logs and metrics from one service\ntype.\n\nExample: If logs or metrics are collected from Elasticsearch, `service.type`\nwould be `elasticsearch`.', - example: 'elasticsearch', - }, - { - name: 'version', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Version of the service the data was collected from.\n\nThis allows to look at a data set only for a specific version of a service.', - example: '3.2.4', - }, - ], - }, - { - name: 'source', - title: 'Source', - group: 2, - description: - 'Source fields describe details about the source of a packet/event.\n\nSource fields are usually populated in conjunction with destination fields.', - type: 'group', - fields: [ - { - name: 'address', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Some event source addresses are defined ambiguously. The event\nwill sometimes list an IP, a domain or a unix socket. You should always store\nthe raw address in the `.address` field.\n\nThen it should be duplicated to `.ip` or `.domain`, depending on which one\nit is.', - }, - { - name: 'as.number', - level: 'extended', - type: 'long', - description: - 'Unique number allocated to the autonomous system. The autonomous\nsystem number (ASN) uniquely identifies each network on the Internet.', - example: 15169, - }, - { - name: 'as.organization.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Organization name.', - example: 'Google LLC', - }, - { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - description: 'Bytes sent from the source to the destination.', - example: 184, - }, - { - name: 'domain', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Source domain.', - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: - 'IP address of the source.\n\nCan be one or multiple IPv4 or IPv6 addresses.', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'MAC address of the source.', - }, - { - name: 'nat.ip', - level: 'extended', - type: 'ip', - description: - 'Translated ip of source based NAT sessions (e.g. internal client\nto internet)\n\nTypically connections traversing load balancers, firewalls, or routers.', - }, - { - name: 'nat.port', - level: 'extended', - type: 'long', - format: 'string', - description: - 'Translated port of source based NAT sessions. (e.g. internal client\nto internet)\n\nTypically used with load balancers, firewalls, or routers.', - }, - { - name: 'packets', - level: 'core', - type: 'long', - description: 'Packets sent from the source to the destination.', - example: 12, - }, - { - name: 'port', - level: 'core', - type: 'long', - format: 'string', - description: 'Port of the source.', - }, - { - name: 'registered_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The highest registered source domain, stripped of the subdomain.\n\nFor example, the registered domain for "foo.google.com" is "google.com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last two labels will not work well for TLDs such as "co.uk".', - example: 'google.com', - }, - { - name: 'top_level_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The effective top level domain (eTLD), also known as the domain\nsuffix, is the last part of the domain name. For example, the top level domain\nfor google.com is "com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last label will not work well for effective TLDs such as "co.uk".', - example: 'co.uk', - }, - { - name: 'user.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the user is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.email', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'User email address.', - }, - { - name: 'user.full_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'user.group.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.group.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'user.group.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - { - name: 'user.hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'user.id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifiers of the user.', - }, - { - name: 'user.name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'threat', - title: 'Threat', - group: 2, - description: - 'Fields to classify events and alerts according to a threat taxonomy\nsuch as the Mitre ATT&CK framework.\n\nThese fields are for users to classify alerts from all of their sources (e.g.\nIDS, NGFW, etc.) within a common taxonomy. The threat.tactic.* are meant to\ncapture the high level category of the threat (e.g. "impact"). The threat.technique.*\nfields are meant to capture which kind of approach is used by this detected\nthreat, to accomplish the goal (e.g. "endpoint denial of service").', - type: 'group', - fields: [ - { - name: 'framework', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the threat framework used to further categorize and classify\nthe tactic and technique of the reported threat. Framework classification\ncan be provided by detecting systems, evaluated at ingest time, or retrospectively\ntagged to events.', - example: 'MITRE ATT&CK', - }, - { - name: 'tactic.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The id of tactic used by this threat. You can use the Mitre ATT&CK\nMatrix Tactic categorization, for example. (ex. https://attack.mitre.org/tactics/TA0040/\n)', - example: 'TA0040', - }, - { - name: 'tactic.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the type of tactic used by this threat. You can use the\nMitre ATT&CK Matrix Tactic categorization, for example. (ex. https://attack.mitre.org/tactics/TA0040/\n)', - example: 'impact', - }, - { - name: 'tactic.reference', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The reference url of tactic used by this threat. You can use the\nMitre ATT&CK Matrix Tactic categorization, for example. (ex. https://attack.mitre.org/tactics/TA0040/\n)', - example: 'https://attack.mitre.org/tactics/TA0040/', - }, - { - name: 'technique.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The id of technique used by this tactic. You can use the Mitre\nATT&CK Matrix Tactic categorization, for example. (ex. https://attack.mitre.org/techniques/T1499/\n)', - example: 'T1499', - }, - { - name: 'technique.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: - 'The name of technique used by this tactic. You can use the Mitre\nATT&CK Matrix Tactic categorization, for example. (ex. https://attack.mitre.org/techniques/T1499/\n)', - example: 'endpoint denial of service', - }, - { - name: 'technique.reference', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The reference url of technique used by this tactic. You can use\nthe Mitre ATT&CK Matrix Tactic categorization, for example. (ex. https://attack.mitre.org/techniques/T1499/\n)', - example: 'https://attack.mitre.org/techniques/T1499/', - }, - ], - }, - { - name: 'tls', - title: 'TLS', - group: 2, - description: - 'Fields related to a TLS connection. These fields focus on the TLS\nprotocol itself and intentionally avoids in-depth analysis of the related x.509\ncertificate files.', - type: 'group', - fields: [ - { - name: 'cipher', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'String indicating the cipher used during the current connection.', - example: 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256', - default_field: false, - }, - { - name: 'client.certificate', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'PEM-encoded stand-alone certificate offered by the client. This\nis usually mutually-exclusive of `client.certificate_chain` since this value\nalso exists in that list.', - example: 'MII...', - default_field: false, - }, - { - name: 'client.certificate_chain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Array of PEM-encoded certificates that make up the certificate\nchain offered by the client. This is usually mutually-exclusive of `client.certificate`\nsince that value should be the first certificate in the chain.', - example: ['MII...', 'MII...'], - default_field: false, - }, - { - name: 'client.hash.md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Certificate fingerprint using the MD5 digest of DER-encoded version\nof certificate offered by the client. For consistency with other hash values,\nthis value should be formatted as an uppercase hash.', - example: '0F76C7F2C55BFD7D8E8B8F4BFBF0C9EC', - default_field: false, - }, - { - name: 'client.hash.sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Certificate fingerprint using the SHA1 digest of DER-encoded version\nof certificate offered by the client. For consistency with other hash values,\nthis value should be formatted as an uppercase hash.', - example: '9E393D93138888D288266C2D915214D1D1CCEB2A', - default_field: false, - }, - { - name: 'client.hash.sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Certificate fingerprint using the SHA256 digest of DER-encoded\nversion of certificate offered by the client. For consistency with other hash\nvalues, this value should be formatted as an uppercase hash.', - example: '0687F666A054EF17A08E2F2162EAB4CBC0D265E1D7875BE74BF3C712CA92DAF0', - default_field: false, - }, - { - name: 'client.issuer', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Distinguished name of subject of the issuer of the x.509 certificate\npresented by the client.', - example: 'CN=MyDomain Root CA, OU=Infrastructure Team, DC=mydomain, DC=com', - default_field: false, - }, - { - name: 'client.ja3', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A hash that identifies clients based on how they perform an SSL/TLS\nhandshake.', - example: 'd4e5b18d6b55c71272893221c96ba240', - default_field: false, - }, - { - name: 'client.not_after', - level: 'extended', - type: 'date', - description: - 'Date/Time indicating when client certificate is no longer considered\nvalid.', - example: '2021-01-01T00:00:00.000Z', - default_field: false, - }, - { - name: 'client.not_before', - level: 'extended', - type: 'date', - description: 'Date/Time indicating when client certificate is first considered\nvalid.', - example: '1970-01-01T00:00:00.000Z', - default_field: false, - }, - { - name: 'client.server_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Also called an SNI, this tells the server which hostname to which\nthe client is attempting to connect. When this value is available, it should\nget copied to `destination.domain`.', - example: 'www.elastic.co', - default_field: false, - }, - { - name: 'client.subject', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Distinguished name of subject of the x.509 certificate presented\nby the client.', - example: 'CN=myclient, OU=Documentation Team, DC=mydomain, DC=com', - default_field: false, - }, - { - name: 'client.supported_ciphers', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Array of ciphers offered by the client during the client hello.', - example: [ - 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384', - 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384', - '...', - ], - default_field: false, - }, - { - name: 'curve', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'String indicating the curve used for the given cipher, when applicable.', - example: 'secp256r1', - default_field: false, - }, - { - name: 'established', - level: 'extended', - type: 'boolean', - description: - 'Boolean flag indicating if the TLS negotiation was successful and\ntransitioned to an encrypted tunnel.', - default_field: false, - }, - { - name: 'next_protocol', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'String indicating the protocol being tunneled. Per the values in\nthe IANA registry (https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids),\nthis string should be lower case.', - example: 'http/1.1', - default_field: false, - }, - { - name: 'resumed', - level: 'extended', - type: 'boolean', - description: - 'Boolean flag indicating if this TLS connection was resumed from\nan existing TLS negotiation.', - default_field: false, - }, - { - name: 'server.certificate', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'PEM-encoded stand-alone certificate offered by the server. This\nis usually mutually-exclusive of `server.certificate_chain` since this value\nalso exists in that list.', - example: 'MII...', - default_field: false, - }, - { - name: 'server.certificate_chain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Array of PEM-encoded certificates that make up the certificate\nchain offered by the server. This is usually mutually-exclusive of `server.certificate`\nsince that value should be the first certificate in the chain.', - example: ['MII...', 'MII...'], - default_field: false, - }, - { - name: 'server.hash.md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Certificate fingerprint using the MD5 digest of DER-encoded version\nof certificate offered by the server. For consistency with other hash values,\nthis value should be formatted as an uppercase hash.', - example: '0F76C7F2C55BFD7D8E8B8F4BFBF0C9EC', - default_field: false, - }, - { - name: 'server.hash.sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Certificate fingerprint using the SHA1 digest of DER-encoded version\nof certificate offered by the server. For consistency with other hash values,\nthis value should be formatted as an uppercase hash.', - example: '9E393D93138888D288266C2D915214D1D1CCEB2A', - default_field: false, - }, - { - name: 'server.hash.sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Certificate fingerprint using the SHA256 digest of DER-encoded\nversion of certificate offered by the server. For consistency with other hash\nvalues, this value should be formatted as an uppercase hash.', - example: '0687F666A054EF17A08E2F2162EAB4CBC0D265E1D7875BE74BF3C712CA92DAF0', - default_field: false, - }, - { - name: 'server.issuer', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Subject of the issuer of the x.509 certificate presented by the\nserver.', - example: 'CN=MyDomain Root CA, OU=Infrastructure Team, DC=mydomain, DC=com', - default_field: false, - }, - { - name: 'server.ja3s', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A hash that identifies servers based on how they perform an SSL/TLS\nhandshake.', - example: '394441ab65754e2207b1e1b457b3641d', - default_field: false, - }, - { - name: 'server.not_after', - level: 'extended', - type: 'date', - description: - 'Timestamp indicating when server certificate is no longer considered\nvalid.', - example: '2021-01-01T00:00:00.000Z', - default_field: false, - }, - { - name: 'server.not_before', - level: 'extended', - type: 'date', - description: 'Timestamp indicating when server certificate is first considered\nvalid.', - example: '1970-01-01T00:00:00.000Z', - default_field: false, - }, - { - name: 'server.subject', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Subject of the x.509 certificate presented by the server.', - example: 'CN=www.mydomain.com, OU=Infrastructure Team, DC=mydomain, DC=com', - default_field: false, - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Numeric part of the version parsed from the original string.', - example: '1.2', - default_field: false, - }, - { - name: 'version_protocol', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Normalized lowercase protocol name parsed from original string.', - example: 'tls', - default_field: false, - }, - ], - }, - { - name: 'tracing', - title: 'Tracing', - group: 2, - description: - 'Distributed tracing makes it possible to analyze performance throughout\na microservice architecture all in one view. This is accomplished by tracing\nall of the requests - from the initial web request in the front-end service\n- to queries made through multiple back-end services.', - type: 'group', - fields: [ - { - name: 'trace.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique identifier of the trace.\n\nA trace groups multiple events like transactions that belong together. For\nexample, a user request handled by multiple inter-connected services.', - example: '4bf92f3577b34da6a3ce929d0e0e4736', - }, - { - name: 'transaction.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique identifier of the transaction.\n\nA transaction is the highest level of work measured within a service, such\nas a request to a server.', - example: '00f067aa0ba902b7', - }, - ], - }, - { - name: 'url', - title: 'URL', - group: 2, - description: - 'URL fields provide support for complete or partial URLs, and supports\nthe breaking down into scheme, domain, path, and so on.', - type: 'group', - fields: [ - { - name: 'domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Domain of the url, such as "www.elastic.co".\n\nIn some cases a URL may refer to an IP and/or port directly, without a domain\nname. In this case, the IP address would go to the `domain` field.', - example: 'www.elastic.co', - }, - { - name: 'extension', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The field contains the file extension from the original request\nurl.\n\nThe file extension is only set if it exists, as not every url has a file extension.\n\nThe leading period must not be included. For example, the value must be "png",\nnot ".png".', - example: 'png', - }, - { - name: 'fragment', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Portion of the url after the `#`, such as "top".\n\nThe `#` is not part of the fragment.', - }, - { - name: 'full', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: - 'If full URLs are important to your use case, they should be stored\nin `url.full`, whether this field is reconstructed or present in the event\nsource.', - example: 'https://www.elastic.co:443/search?q=elasticsearch#top', - }, - { - name: 'original', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: - 'Unmodified original url as seen in the event source.\n\nNote that in network monitoring, the observed URL may be a full URL, whereas\nin access logs, the URL is often just represented as a path.\n\nThis field is meant to represent the URL as it was observed, complete or not.', - example: - 'https://www.elastic.co:443/search?q=elasticsearch#top or /search?q=elasticsearch', - }, - { - name: 'password', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Password of the request.', - }, - { - name: 'path', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Path of the request, such as "/search".', - }, - { - name: 'port', - level: 'extended', - type: 'long', - format: 'string', - description: 'Port of the request, such as 443.', - example: 443, - }, - { - name: 'query', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The query field describes the query string of the request, such\nas "q=elasticsearch".\n\nThe `?` is excluded from the query string. If a URL contains no `?`, there\nis no query field. If there is a `?` but no query, the query field exists\nwith an empty string. The `exists` query can be used to differentiate between\nthe two cases.', - }, - { - name: 'registered_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The highest registered url domain, stripped of the subdomain.\n\nFor example, the registered domain for "foo.google.com" is "google.com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last two labels will not work well for TLDs such as "co.uk".', - example: 'google.com', - }, - { - name: 'scheme', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Scheme of the request, such as "https".\n\nNote: The `:` is not part of the scheme.', - example: 'https', - }, - { - name: 'top_level_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The effective top level domain (eTLD), also known as the domain\nsuffix, is the last part of the domain name. For example, the top level domain\nfor google.com is "com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last label will not work well for effective TLDs such as "co.uk".', - example: 'co.uk', - }, - { - name: 'username', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Username of the request.', - }, - ], - }, - { - name: 'user', - title: 'User', - group: 2, - description: - 'The user fields describe information about the user that is relevant\nto the event.\n\nFields can have one entry or multiple entries. If a user has more than one id,\nprovide an array that includes all of them.', - type: 'group', - fields: [ - { - name: 'domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the user is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'email', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'User email address.', - }, - { - name: 'full_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'group.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'group.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'group.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - { - name: 'hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifiers of the user.', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'user_agent', - title: 'User agent', - group: 2, - description: - 'The user_agent fields normally come from a browser request.\n\nThey often show up in web service logs coming from the parsed user agent string.', - type: 'group', - fields: [ - { - name: 'device.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the device.', - example: 'iPhone', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the user agent.', - example: 'Safari', - }, - { - name: 'original', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: 'Unparsed user_agent string.', - example: - 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15\n(KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1', - }, - { - name: 'os.family', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'OS family (such as redhat, debian, freebsd, windows).', - example: 'debian', - }, - { - name: 'os.full', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, including the version or code name.', - example: 'Mac OS Mojave', - }, - { - name: 'os.kernel', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system kernel version as a raw string.', - example: '4.4.0-112-generic', - }, - { - name: 'os.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, without the version.', - example: 'Mac OS X', - }, - { - name: 'os.platform', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'os.version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system version as a raw string.', - example: '10.14.1', - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Version of the user agent.', - example: 12, - }, - ], - }, - { - name: 'vlan', - title: 'VLAN', - group: 2, - description: - 'The VLAN fields are used to identify 802.1q tag(s) of a packet,\nas well as ingress and egress VLAN associations of an observer in relation to\na specific packet or connection.\n\nNetwork.vlan fields are used to record a single VLAN tag, or the outer tag in\nthe case of q-in-q encapsulations, for a packet or connection as observed, typically\nprovided by a network sensor (e.g. Zeek, Wireshark) passively reporting on traffic.\n\nNetwork.inner VLAN fields are used to report inner q-in-q 802.1q tags (multiple\n802.1q encapsulations) as observed, typically provided by a network sensor (e.g.\nZeek, Wireshark) passively reporting on traffic. Network.inner VLAN fields should\nonly be used in addition to network.vlan fields to indicate q-in-q tagging.\n\nObserver.ingress and observer.egress VLAN values are used to record observer\nspecific information when observer events contain discrete ingress and egress\nVLAN information, typically provided by firewalls, routers, or load balancers.', - type: 'group', - fields: [ - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'VLAN ID as reported by the observer.', - example: 10, - default_field: false, - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Optional VLAN name as reported by the observer.', - example: 'outside', - default_field: false, - }, - ], - }, - { - name: 'vulnerability', - title: 'Vulnerability', - group: 2, - description: - 'The vulnerability fields describe information about a vulnerability\nthat is relevant to an event.', - type: 'group', - fields: [ - { - name: 'category', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The type of system or architecture that the vulnerability affects.\nThese may be platform-specific (for example, Debian or SUSE) or general (for\nexample, Database or Firewall). For example (https://qualysguard.qualys.com/qwebhelp/fo_portal/knowledgebase/vulnerability_categories.htm[Qualys\nvulnerability categories])\n\nThis field must be an array.', - example: '["Firewall"]', - default_field: false, - }, - { - name: 'classification', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The classification of the vulnerability scoring system. For example\n(https://www.first.org/cvss/)', - example: 'CVSS', - default_field: false, - }, - { - name: 'description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: - 'The description of the vulnerability that provides additional context\nof the vulnerability. For example (https://cve.mitre.org/about/faqs.html#cve_entry_descriptions_created[Common\nVulnerabilities and Exposure CVE description])', - example: 'In macOS before 2.12.6, there is a vulnerability in the RPC...', - default_field: false, - }, - { - name: 'enumeration', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The type of identifier used for this vulnerability. For example\n(https://cve.mitre.org/about/)', - example: 'CVE', - default_field: false, - }, - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The identification (ID) is the number portion of a vulnerability\nentry. It includes a unique identification number for the vulnerability. For\nexample (https://cve.mitre.org/about/faqs.html#what_is_cve_id)[Common Vulnerabilities\nand Exposure CVE ID]', - example: 'CVE-2019-00001', - default_field: false, - }, - { - name: 'reference', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A resource that provides additional information, context, and mitigations\nfor the identified vulnerability.', - example: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-6111', - default_field: false, - }, - { - name: 'report_id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The report or scan identification number.', - example: 20191018.0001, - default_field: false, - }, - { - name: 'scanner.vendor', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The name of the vulnerability scanner vendor.', - example: 'Tenable', - default_field: false, - }, - { - name: 'score.base', - level: 'extended', - type: 'float', - description: - 'Scores can range from 0.0 to 10.0, with 10.0 being the most severe.\n\nBase scores cover an assessment for exploitability metrics (attack vector,\ncomplexity, privileges, and user interaction), impact metrics (confidentiality,\nintegrity, and availability), and scope. For example (https://www.first.org/cvss/specification-document)', - example: 5.5, - default_field: false, - }, - { - name: 'score.environmental', - level: 'extended', - type: 'float', - description: - 'Scores can range from 0.0 to 10.0, with 10.0 being the most severe.\n\nEnvironmental scores cover an assessment for any modified Base metrics, confidentiality,\nintegrity, and availability requirements. For example (https://www.first.org/cvss/specification-document)', - example: 5.5, - default_field: false, - }, - { - name: 'score.temporal', - level: 'extended', - type: 'float', - description: - 'Scores can range from 0.0 to 10.0, with 10.0 being the most severe.\n\nTemporal scores cover an assessment for code maturity, remediation level,\nand confidence. For example (https://www.first.org/cvss/specification-document)', - default_field: false, - }, - { - name: 'score.version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The National Vulnerability Database (NVD) provides qualitative\nseverity rankings of "Low", "Medium", and "High" for CVSS v2.0 base score\nranges in addition to the severity ratings for CVSS v3.0 as they are defined\nin the CVSS v3.0 specification.\n\nCVSS is owned and managed by FIRST.Org, Inc. (FIRST), a US-based non-profit\norganization, whose mission is to help computer security incident response\nteams across the world. For example (https://nvd.nist.gov/vuln-metrics/cvss)', - example: 2, - default_field: false, - }, - { - name: 'severity', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The severity of the vulnerability can help with metrics and internal\nprioritization regarding remediation. For example (https://nvd.nist.gov/vuln-metrics/cvss)', - example: 'Critical', - default_field: false, - }, - ], - }, - ], - }, - { - key: 'beat', - anchor: 'beat-common', - title: 'Beat', - description: 'Contains common beat fields available in all event types.\n', - fields: [ - { - name: 'agent.hostname', - type: 'keyword', - description: 'Hostname of the agent.', - }, - { - name: 'beat.timezone', - type: 'alias', - path: 'event.timezone', - migration: true, - }, - { - name: 'fields', - type: 'object', - object_type: 'keyword', - description: 'Contains user configurable fields.\n', - }, - { - name: 'beat.name', - type: 'alias', - path: 'host.name', - migration: true, - }, - { - name: 'beat.hostname', - type: 'alias', - path: 'agent.hostname', - migration: true, - }, - { - name: 'timeseries.instance', - type: 'keyword', - description: 'Time series instance id', - }, - ], - }, - { - key: 'cloud', - title: 'Cloud provider metadata', - description: 'Metadata from cloud providers added by the add_cloud_metadata processor.\n', - fields: [ - { - name: 'cloud.project.id', - example: 'project-x', - description: 'Name of the project in Google Cloud.\n', - }, - { - name: 'cloud.image.id', - example: 'ami-abcd1234', - description: 'Image ID for the cloud instance.\n', - }, - { - name: 'meta.cloud.provider', - type: 'alias', - path: 'cloud.provider', - migration: true, - }, - { - name: 'meta.cloud.instance_id', - type: 'alias', - path: 'cloud.instance.id', - migration: true, - }, - { - name: 'meta.cloud.instance_name', - type: 'alias', - path: 'cloud.instance.name', - migration: true, - }, - { - name: 'meta.cloud.machine_type', - type: 'alias', - path: 'cloud.machine.type', - migration: true, - }, - { - name: 'meta.cloud.availability_zone', - type: 'alias', - path: 'cloud.availability_zone', - migration: true, - }, - { - name: 'meta.cloud.project_id', - type: 'alias', - path: 'cloud.project.id', - migration: true, - }, - { - name: 'meta.cloud.region', - type: 'alias', - path: 'cloud.region', - migration: true, - }, - ], - }, - { - key: 'docker', - title: 'Docker', - description: 'Docker stats collected from Docker.\n', - short_config: false, - anchor: 'docker-processor', - fields: [ - { - name: 'docker', - type: 'group', - fields: [ - { - name: 'container.id', - type: 'alias', - path: 'container.id', - migration: true, - }, - { - name: 'container.image', - type: 'alias', - path: 'container.image.name', - migration: true, - }, - { - name: 'container.name', - type: 'alias', - path: 'container.name', - migration: true, - }, - { - name: 'container.labels', - type: 'object', - object_type: 'keyword', - description: 'Image labels.\n', - }, - ], - }, - ], - }, - { - key: 'host', - title: 'Host', - description: 'Info collected for the host machine.\n', - anchor: 'host-processor', - fields: [ - { - name: 'host', - type: 'group', - fields: [ - { - name: 'containerized', - type: 'boolean', - description: 'If the host is a container.\n', - }, - { - name: 'os.build', - type: 'keyword', - example: '18D109', - description: 'OS build information.\n', - }, - { - name: 'os.codename', - type: 'keyword', - example: 'stretch', - description: 'OS codename, if any.\n', - }, - ], - }, - ], - }, - { - key: 'kubernetes', - title: 'Kubernetes', - description: 'Kubernetes metadata added by the kubernetes processor\n', - short_config: false, - anchor: 'kubernetes-processor', - fields: [ - { - name: 'kubernetes', - type: 'group', - fields: [ - { - name: 'pod.name', - type: 'keyword', - description: 'Kubernetes pod name\n', - }, - { - name: 'pod.uid', - type: 'keyword', - description: 'Kubernetes Pod UID\n', - }, - { - name: 'namespace', - type: 'keyword', - description: 'Kubernetes namespace\n', - }, - { - name: 'node.name', - type: 'keyword', - description: 'Kubernetes node name\n', - }, - { - name: 'labels.*', - type: 'object', - object_type: 'keyword', - object_type_mapping_type: '*', - description: 'Kubernetes labels map\n', - }, - { - name: 'annotations.*', - type: 'object', - object_type: 'keyword', - object_type_mapping_type: '*', - description: 'Kubernetes annotations map\n', - }, - { - name: 'replicaset.name', - type: 'keyword', - description: 'Kubernetes replicaset name\n', - }, - { - name: 'deployment.name', - type: 'keyword', - description: 'Kubernetes deployment name\n', - }, - { - name: 'statefulset.name', - type: 'keyword', - description: 'Kubernetes statefulset name\n', - }, - { - name: 'container.name', - type: 'keyword', - description: 'Kubernetes container name\n', - }, - { - name: 'container.image', - type: 'keyword', - description: 'Kubernetes container image\n', - }, - ], - }, - ], - }, - { - key: 'process', - title: 'Process', - description: 'Process metadata fields\n', - fields: [ - { - name: 'process', - type: 'group', - fields: [ - { - name: 'exe', - type: 'alias', - path: 'process.executable', - migration: true, - }, - ], - }, - ], - }, - { - key: 'jolokia-autodiscover', - title: 'Jolokia Discovery autodiscover provider', - description: 'Metadata from Jolokia Discovery added by the jolokia provider.\n', - fields: [ - { - name: 'jolokia.agent.version', - type: 'keyword', - description: 'Version number of jolokia agent.\n', - }, - { - name: 'jolokia.agent.id', - type: 'keyword', - description: - 'Each agent has a unique id which can be either provided during startup of the agent in form of a configuration parameter or being autodetected. If autodected, the id has several parts: The IP, the process id, hashcode of the agent and its type.\n', - }, - { - name: 'jolokia.server.product', - type: 'keyword', - description: 'The container product if detected.\n', - }, - { - name: 'jolokia.server.version', - type: 'keyword', - description: "The container's version (if detected).\n", - }, - { - name: 'jolokia.server.vendor', - type: 'keyword', - description: 'The vendor of the container the agent is running in.\n', - }, - { - name: 'jolokia.url', - type: 'keyword', - description: 'The URL how this agent can be contacted.\n', - }, - { - name: 'jolokia.secured', - type: 'boolean', - description: 'Whether the agent was configured for authentication or not.\n', - }, - ], - }, - { - key: 'log', - title: 'Log file content', - description: 'Contains log file lines.\n', - fields: [ - { - name: 'log.file.path', - type: 'keyword', - required: false, - description: - 'The file from which the line was read. This field contains the absolute path to the file. For example: `/var/log/system.log`.\n', - }, - { - name: 'log.source.address', - type: 'keyword', - required: false, - description: 'Source address from which the log event was read / sent from.\n', - }, - { - name: 'log.offset', - type: 'long', - required: false, - description: 'The file offset the reported line starts at.\n', - }, - { - name: 'stream', - type: 'keyword', - required: false, - description: "Log stream when reading container logs, can be 'stdout' or 'stderr'\n", - }, - { - name: 'input.type', - required: true, - description: - 'The input type from which the event was generated. This field is set to the value specified for the `type` option in the input section of the Filebeat config file.\n', - }, - { - name: 'syslog.facility', - type: 'long', - required: false, - description: 'The facility extracted from the priority.\n', - }, - { - name: 'syslog.priority', - type: 'long', - required: false, - description: 'The priority of the syslog event.\n', - }, - { - name: 'syslog.severity_label', - type: 'keyword', - required: false, - description: 'The human readable severity.\n', - }, - { - name: 'syslog.facility_label', - type: 'keyword', - required: false, - description: 'The human readable facility.\n', - }, - { - name: 'process.program', - type: 'keyword', - required: false, - description: 'The name of the program.\n', - }, - { - name: 'log.flags', - description: 'This field contains the flags of the event.\n', - }, - { - name: 'http.response.content_length', - type: 'alias', - path: 'http.response.body.bytes', - migration: true, - }, - { - name: 'user_agent', - type: 'group', - fields: [ - { - name: 'os', - type: 'group', - fields: [ - { - name: 'full_name', - type: 'keyword', - }, - ], - }, - ], - }, - { - name: 'fileset.name', - type: 'keyword', - description: 'The Filebeat fileset that generated this event.\n', - }, - { - name: 'fileset.module', - type: 'alias', - path: 'event.module', - migration: true, - }, - { - name: 'read_timestamp', - type: 'alias', - path: 'event.created', - migration: true, - }, - { - name: 'docker.attrs', - type: 'object', - object_type: 'keyword', - description: - "docker.attrs contains labels and environment variables written by docker's JSON File logging driver. These fields are only available when they are configured in the logging driver options.\n", - }, - { - name: 'icmp.code', - type: 'keyword', - description: 'ICMP code.\n', - }, - { - name: 'icmp.type', - type: 'keyword', - description: 'ICMP type.\n', - }, - { - name: 'igmp.type', - type: 'keyword', - description: 'IGMP type.\n', - }, - { - name: 'azure', - type: 'group', - fields: [ - { - name: 'eventhub', - type: 'keyword', - description: 'Name of the eventhub.\n', - }, - { - name: 'offset', - type: 'long', - description: 'The offset.\n', - }, - { - name: 'enqueued_time', - type: 'date', - description: 'The enqueued time.\n', - }, - { - name: 'partition_id', - type: 'long', - description: 'The partition id.\n', - }, - { - name: 'consumer_group', - type: 'keyword', - description: 'The consumer group.\n', - }, - { - name: 'sequence_number', - type: 'long', - description: 'The sequence number.\n', - }, - ], - }, - { - name: 'kafka', - type: 'group', - fields: [ - { - name: 'topic', - type: 'keyword', - description: 'Kafka topic\n', - }, - { - name: 'partition', - type: 'long', - description: 'Kafka partition number\n', - }, - { - name: 'offset', - type: 'long', - description: 'Kafka offset of this message\n', - }, - { - name: 'key', - type: 'keyword', - description: 'Kafka key, corresponding to the Kafka value stored in the message\n', - }, - { - name: 'block_timestamp', - type: 'date', - description: 'Kafka outer (compressed) block timestamp\n', - }, - { - name: 'headers', - type: 'array', - description: - 'An array of Kafka header strings for this message, in the form ": ".\n', - }, - ], - }, - ], - }, - { - key: 'apache', - title: 'Apache', - description: 'Apache Module\n', - short_config: true, - fields: [ - { - name: 'apache2', - type: 'group', - description: 'Aliases for backward compatibility with old apache2 fields\n', - fields: [ - { - name: 'access', - type: 'group', - fields: [ - { - name: 'remote_ip', - type: 'alias', - path: 'source.address', - migration: true, - }, - { - name: 'ssl.protocol', - type: 'alias', - path: 'apache.access.ssl.protocol', - migration: true, - }, - { - name: 'ssl.cipher', - type: 'alias', - path: 'apache.access.ssl.cipher', - migration: true, - }, - { - name: 'body_sent.bytes', - type: 'alias', - path: 'http.response.body.bytes', - migration: true, - }, - { - name: 'user_name', - type: 'alias', - path: 'user.name', - migration: true, - }, - { - name: 'method', - type: 'alias', - path: 'http.request.method', - migration: true, - }, - { - name: 'url', - type: 'alias', - path: 'url.original', - migration: true, - }, - { - name: 'http_version', - type: 'alias', - path: 'http.version', - migration: true, - }, - { - name: 'response_code', - type: 'alias', - path: 'http.response.status_code', - migration: true, - }, - { - name: 'referrer', - type: 'alias', - path: 'http.request.referrer', - migration: true, - }, - { - name: 'agent', - type: 'alias', - path: 'user_agent.original', - migration: true, - }, - { - name: 'user_agent', - type: 'group', - fields: [ - { - name: 'device', - type: 'alias', - path: 'user_agent.device.name', - migration: true, - }, - { - name: 'name', - type: 'alias', - path: 'user_agent.name', - migration: true, - }, - { - name: 'os', - type: 'alias', - path: 'user_agent.os.full_name', - migration: true, - }, - { - name: 'os_name', - type: 'alias', - path: 'user_agent.os.name', - migration: true, - }, - { - name: 'original', - type: 'alias', - path: 'user_agent.original', - migration: true, - }, - ], - }, - { - name: 'geoip', - type: 'group', - fields: [ - { - name: 'continent_name', - type: 'alias', - path: 'source.geo.continent_name', - migration: true, - }, - { - name: 'country_iso_code', - type: 'alias', - path: 'source.geo.country_iso_code', - migration: true, - }, - { - name: 'location', - type: 'alias', - path: 'source.geo.location', - migration: true, - }, - { - name: 'region_name', - type: 'alias', - path: 'source.geo.region_name', - migration: true, - }, - { - name: 'city_name', - type: 'alias', - path: 'source.geo.city_name', - migration: true, - }, - { - name: 'region_iso_code', - type: 'alias', - path: 'source.geo.region_iso_code', - migration: true, - }, - ], - }, - ], - }, - { - name: 'error', - type: 'group', - fields: [ - { - name: 'level', - type: 'alias', - path: 'log.level', - migration: true, - }, - { - name: 'message', - type: 'alias', - path: 'message', - migration: true, - }, - { - name: 'pid', - type: 'alias', - path: 'process.pid', - migration: true, - }, - { - name: 'tid', - type: 'alias', - path: 'process.thread.id', - migration: true, - }, - { - name: 'module', - type: 'alias', - path: 'apache.error.module', - migration: true, - }, - ], - }, - ], - }, - { - name: 'apache', - type: 'group', - description: 'Apache fields.\n', - fields: [ - { - name: 'access', - type: 'group', - description: 'Contains fields for the Apache HTTP Server access logs.\n', - fields: [ - { - name: 'ssl.protocol', - type: 'keyword', - description: 'SSL protocol version.\n', - }, - { - name: 'ssl.cipher', - type: 'keyword', - description: 'SSL cipher name.\n', - }, - ], - }, - { - name: 'error', - type: 'group', - description: 'Fields from the Apache error logs.\n', - fields: [ - { - name: 'module', - type: 'keyword', - description: 'The module producing the logged message.\n', - }, - ], - }, - ], - }, - ], - }, - { - key: 'auditd', - title: 'Auditd', - description: 'Module for parsing auditd logs.\n', - short_config: true, - fields: [ - { - name: 'user', - type: 'group', - fields: [ - { - name: 'terminal', - type: 'keyword', - description: - 'Terminal or tty device on which the user is performing the observed activity.\n', - }, - { - name: 'audit', - type: 'group', - fields: [ - { - name: 'id', - type: 'keyword', - description: 'One or multiple unique identifiers of the user.\n', - }, - { - name: 'name', - type: 'keyword', - example: 'albert', - description: 'Short name or login of the user.\n', - }, - { - name: 'group.id', - type: 'keyword', - description: 'Unique identifier for the group on the system/platform.\n', - }, - { - name: 'group.name', - type: 'keyword', - description: 'Name of the group.\n', - }, - ], - }, - { - name: 'effective', - type: 'group', - fields: [ - { - name: 'id', - type: 'keyword', - description: 'One or multiple unique identifiers of the user.\n', - }, - { - name: 'name', - type: 'keyword', - example: 'albert', - description: 'Short name or login of the user.\n', - }, - { - name: 'group.id', - type: 'keyword', - description: 'Unique identifier for the group on the system/platform.\n', - }, - { - name: 'group.name', - type: 'keyword', - description: 'Name of the group.\n', - }, - ], - }, - { - name: 'filesystem', - type: 'group', - fields: [ - { - name: 'id', - type: 'keyword', - description: 'One or multiple unique identifiers of the user.\n', - }, - { - name: 'name', - type: 'keyword', - example: 'albert', - description: 'Short name or login of the user.\n', - }, - { - name: 'group.id', - type: 'keyword', - description: 'Unique identifier for the group on the system/platform.\n', - }, - { - name: 'group.name', - type: 'keyword', - description: 'Name of the group.\n', - }, - ], - }, - { - name: 'owner', - type: 'group', - fields: [ - { - name: 'id', - type: 'keyword', - description: 'One or multiple unique identifiers of the user.\n', - }, - { - name: 'name', - type: 'keyword', - example: 'albert', - description: 'Short name or login of the user.\n', - }, - { - name: 'group.id', - type: 'keyword', - description: 'Unique identifier for the group on the system/platform.\n', - }, - { - name: 'group.name', - type: 'keyword', - description: 'Name of the group.\n', - }, - ], - }, - { - name: 'saved', - type: 'group', - fields: [ - { - name: 'id', - type: 'keyword', - description: 'One or multiple unique identifiers of the user.\n', - }, - { - name: 'name', - type: 'keyword', - example: 'albert', - description: 'Short name or login of the user.\n', - }, - { - name: 'group.id', - type: 'keyword', - description: 'Unique identifier for the group on the system/platform.\n', - }, - { - name: 'group.name', - type: 'keyword', - description: 'Name of the group.\n', - }, - ], - }, - ], - }, - { - name: 'auditd', - type: 'group', - description: 'Fields from the auditd logs.\n', - fields: [ - { - name: 'log', - type: 'group', - description: - 'Fields from the Linux audit log. Not all fields are documented here because they are dynamic and vary by audit event type.\n', - fields: [ - { - name: 'old_auid', - description: - 'For login events this is the old audit ID used for the user prior to this login.\n', - }, - { - name: 'new_auid', - description: - 'For login events this is the new audit ID. The audit ID can be used to trace future events to the user even if their identity changes (like becoming root).\n', - }, - { - name: 'old_ses', - description: - 'For login events this is the old session ID used for the user prior to this login.\n', - }, - { - name: 'new_ses', - description: - 'For login events this is the new session ID. It can be used to tie a user to future events by session ID.\n', - }, - { - name: 'sequence', - type: 'long', - description: 'The audit event sequence number.\n', - }, - { - name: 'items', - description: 'The number of items in an event.\n', - }, - { - name: 'item', - description: - 'The item field indicates which item out of the total number of items. This number is zero-based; a value of 0 means it is the first item.\n', - }, - { - name: 'tty', - type: 'keyword', - definition: 'TTY udevice the user is running programs on.\n', - }, - { - name: 'a0', - description: 'The first argument to the system call.\n', - }, - { - name: 'addr', - type: 'ip', - definition: 'Remote address that the user is connecting from.\n', - }, - { - name: 'rport', - type: 'long', - definition: 'Remote port number.\n', - }, - { - name: 'laddr', - type: 'ip', - definition: 'Local network address.\n', - }, - { - name: 'lport', - type: 'long', - definition: 'Local port number.\n', - }, - { - name: 'acct', - type: 'alias', - path: 'user.name', - migration: true, - }, - { - name: 'pid', - type: 'alias', - path: 'process.pid', - migration: true, - }, - { - name: 'ppid', - type: 'alias', - path: 'process.ppid', - migration: true, - }, - { - name: 'res', - type: 'alias', - path: 'event.outcome', - migration: true, - }, - { - name: 'record_type', - type: 'alias', - path: 'event.action', - migration: true, - }, - { - name: 'geoip', - type: 'group', - fields: [ - { - name: 'continent_name', - type: 'alias', - path: 'source.geo.continent_name', - migration: true, - }, - { - name: 'country_iso_code', - type: 'alias', - path: 'source.geo.country_iso_code', - migration: true, - }, - { - name: 'location', - type: 'alias', - path: 'source.geo.location', - migration: true, - }, - { - name: 'region_name', - type: 'alias', - path: 'source.geo.region_name', - migration: true, - }, - { - name: 'city_name', - type: 'alias', - path: 'source.geo.city_name', - migration: true, - }, - { - name: 'region_iso_code', - type: 'alias', - path: 'source.geo.region_iso_code', - migration: true, - }, - ], - }, - { - name: 'arch', - type: 'alias', - path: 'host.architecture', - migration: true, - }, - { - name: 'gid', - type: 'alias', - path: 'user.group.id', - migration: true, - }, - { - name: 'uid', - type: 'alias', - path: 'user.id', - migration: true, - }, - { - name: 'agid', - type: 'alias', - path: 'user.audit.group.id', - migration: true, - }, - { - name: 'auid', - type: 'alias', - path: 'user.audit.id', - migration: true, - }, - { - name: 'fsgid', - type: 'alias', - path: 'user.filesystem.group.id', - migration: true, - }, - { - name: 'fsuid', - type: 'alias', - path: 'user.filesystem.id', - migration: true, - }, - { - name: 'egid', - type: 'alias', - path: 'user.effective.group.id', - migration: true, - }, - { - name: 'euid', - type: 'alias', - path: 'user.effective.id', - migration: true, - }, - { - name: 'sgid', - type: 'alias', - path: 'user.saved.group.id', - migration: true, - }, - { - name: 'suid', - type: 'alias', - path: 'user.saved.id', - migration: true, - }, - { - name: 'ogid', - type: 'alias', - path: 'user.owner.group.id', - migration: true, - }, - { - name: 'ouid', - type: 'alias', - path: 'user.owner.id', - migration: true, - }, - { - name: 'comm', - type: 'alias', - path: 'process.name', - migration: true, - }, - { - name: 'exe', - type: 'alias', - path: 'process.executable', - migration: true, - }, - { - name: 'terminal', - type: 'alias', - path: 'user.terminal', - migration: true, - }, - { - name: 'msg', - type: 'alias', - path: 'message', - migration: true, - }, - { - name: 'src', - type: 'alias', - path: 'source.address', - migration: true, - }, - { - name: 'dst', - type: 'alias', - path: 'destination.address', - migration: true, - }, - ], - }, - ], - }, - ], - }, - { - key: 'elasticsearch', - title: 'elasticsearch', - description: 'elasticsearch Module\n', - fields: [ - { - name: 'elasticsearch', - type: 'group', - description: '\n', - fields: [ - { - name: 'component', - description: 'Elasticsearch component from where the log event originated', - example: 'o.e.c.m.MetaDataCreateIndexService', - type: 'keyword', - }, - { - name: 'cluster.uuid', - description: 'UUID of the cluster', - example: 'GmvrbHlNTiSVYiPf8kxg9g', - type: 'keyword', - }, - { - name: 'cluster.name', - description: 'Name of the cluster', - example: 'docker-cluster', - type: 'keyword', - }, - { - name: 'node.id', - description: 'ID of the node', - example: 'DSiWcTyeThWtUXLB9J0BMw', - type: 'keyword', - }, - { - name: 'node.name', - description: 'Name of the node', - example: 'vWNJsZ3', - type: 'keyword', - }, - { - name: 'index.name', - description: 'Index name', - example: 'filebeat-test-input', - type: 'keyword', - }, - { - name: 'index.id', - description: 'Index id', - example: 'aOGgDwbURfCV57AScqbCgw', - type: 'keyword', - }, - { - name: 'shard.id', - description: 'Id of the shard', - example: '0', - type: 'keyword', - }, - { - name: 'audit', - type: 'group', - description: '\n', - fields: [ - { - name: 'layer', - description: - 'The layer from which this event originated: rest, transport or ip_filter', - example: 'rest', - type: 'keyword', - }, - { - name: 'event_type', - description: - 'The type of event that occurred: anonymous_access_denied, authentication_failed, access_denied, access_granted, connection_granted, connection_denied, tampered_request, run_as_granted, run_as_denied', - example: 'access_granted', - type: 'keyword', - }, - { - name: 'origin.type', - description: - 'Where the request originated: rest (request originated from a REST API request), transport (request was received on the transport channel), local_node (the local node issued the request)', - example: 'local_node', - type: 'keyword', - }, - { - name: 'realm', - description: 'The authentication realm the authentication was validated against', - example: 'default_file', - type: 'keyword', - }, - { - name: 'user.realm', - description: "The user's authentication realm, if authenticated", - example: 'active_directory', - type: 'keyword', - }, - { - name: 'user.roles', - description: 'Roles to which the principal belongs', - example: ['kibana_user', 'beats_admin'], - type: 'keyword', - }, - { - name: 'action', - description: 'The name of the action that was executed', - example: 'cluster:monitor/main', - type: 'keyword', - }, - { - name: 'url.params', - description: 'REST URI parameters', - example: '{username=jacknich2}', - }, - { - name: 'indices', - description: 'Indices accessed by action', - example: ['foo-2019.01.04', 'foo-2019.01.03', 'foo-2019.01.06'], - type: 'keyword', - }, - { - name: 'request.id', - description: 'Unique ID of request', - example: 'WzL_kb6VSvOhAq0twPvHOQ', - type: 'keyword', - }, - { - name: 'request.name', - description: 'The type of request that was executed', - example: 'ClearScrollRequest', - type: 'keyword', - }, - { - name: 'request_body', - type: 'alias', - path: 'http.request.body.content', - migration: true, - }, - { - name: 'origin_address', - type: 'alias', - path: 'source.ip', - migration: true, - }, - { - name: 'uri', - type: 'alias', - path: 'url.original', - migration: true, - }, - { - name: 'principal', - type: 'alias', - path: 'user.name', - migration: true, - }, - { - name: 'message', - type: 'text', - }, - ], - }, - { - name: 'gc', - type: 'group', - description: 'GC fileset fields.\n', - fields: [ - { - name: 'phase', - type: 'group', - description: 'Fields specific to GC phase.\n', - fields: [ - { - name: 'name', - type: 'keyword', - description: 'Name of the GC collection phase.\n', - }, - { - name: 'duration_sec', - type: 'float', - description: - 'Collection phase duration according to the Java virtual machine.\n', - }, - { - name: 'scrub_symbol_table_time_sec', - type: 'float', - description: 'Pause time in seconds cleaning up symbol tables.\n', - }, - { - name: 'scrub_string_table_time_sec', - type: 'float', - description: 'Pause time in seconds cleaning up string tables.\n', - }, - { - name: 'weak_refs_processing_time_sec', - type: 'float', - description: 'Time spent processing weak references in seconds.\n', - }, - { - name: 'parallel_rescan_time_sec', - type: 'float', - description: - 'Time spent in seconds marking live objects while application is stopped.\n', - }, - { - name: 'class_unload_time_sec', - type: 'float', - description: 'Time spent unloading unused classes in seconds.\n', - }, - { - name: 'cpu_time', - type: 'group', - description: 'Process CPU time spent performing collections.\n', - fields: [ - { - name: 'user_sec', - type: 'float', - description: 'CPU time spent outside the kernel.\n', - }, - { - name: 'sys_sec', - type: 'float', - description: 'CPU time spent inside the kernel.\n', - }, - { - name: 'real_sec', - type: 'float', - description: - 'Total elapsed CPU time spent to complete the collection from start to finish.\n', - }, - ], - }, - ], - }, - { - name: 'jvm_runtime_sec', - type: 'float', - description: 'The time from JVM start up in seconds, as a floating point number.\n', - }, - { - name: 'threads_total_stop_time_sec', - type: 'float', - description: 'Garbage collection threads total stop time seconds.\n', - }, - { - name: 'stopping_threads_time_sec', - type: 'float', - description: 'Time took to stop threads seconds.\n', - }, - { - name: 'tags', - type: 'keyword', - description: 'GC logging tags.\n', - }, - { - name: 'heap', - type: 'group', - description: 'Heap allocation and total size.\n', - fields: [ - { - name: 'size_kb', - type: 'integer', - description: 'Total heap size in kilobytes.\n', - }, - { - name: 'used_kb', - type: 'integer', - description: 'Used heap in kilobytes.\n', - }, - ], - }, - { - name: 'old_gen', - type: 'group', - description: 'Old generation occupancy and total size.\n', - fields: [ - { - name: 'size_kb', - type: 'integer', - description: 'Total size of old generation in kilobytes.\n', - }, - { - name: 'used_kb', - type: 'integer', - description: 'Old generation occupancy in kilobytes.\n', - }, - ], - }, - { - name: 'young_gen', - type: 'group', - description: 'Young generation occupancy and total size.\n', - fields: [ - { - name: 'size_kb', - type: 'integer', - description: 'Total size of young generation in kilobytes.\n', - }, - { - name: 'used_kb', - type: 'integer', - description: 'Young generation occupancy in kilobytes.\n', - }, - ], - }, - ], - }, - { - name: 'server', - description: 'Server log file', - type: 'group', - fields: [ - { - name: 'stacktrace', - description: 'Stack trace in case of errors', - index: false, - }, - { - name: 'gc', - description: 'GC log', - type: 'group', - fields: [ - { - name: 'young', - description: 'Young GC', - example: '', - type: 'group', - fields: [ - { - name: 'one', - description: '', - example: '', - type: 'long', - }, - { - name: 'two', - description: '', - example: '', - type: 'long', - }, - ], - }, - { - name: 'overhead_seq', - description: 'Sequence number', - example: 3449992, - type: 'long', - }, - { - name: 'collection_duration.ms', - description: 'Time spent in GC, in milliseconds', - example: 1600, - type: 'float', - }, - { - name: 'observation_duration.ms', - description: 'Total time over which collection was observed, in milliseconds', - example: 1800, - type: 'float', - }, - ], - }, - ], - }, - { - name: 'slowlog', - description: 'Slowlog events from Elasticsearch', - example: - '[2018-06-29T10:06:14,933][INFO ][index.search.slowlog.query] [v_VJhjV] [metricbeat-6.3.0-2018.06.26][0] took[4.5ms], took_millis[4], total_hits[19435], types[], stats[], search_type[QUERY_THEN_FETCH], total_shards[1], source[{"query":{"match_all":{"boost":1.0}}}],', - type: 'group', - fields: [ - { - name: 'logger', - description: 'Logger name', - example: 'index.search.slowlog.fetch', - type: 'keyword', - }, - { - name: 'took', - description: 'Time it took to execute the query', - example: '300ms', - type: 'keyword', - }, - { - name: 'types', - description: 'Types', - example: '', - type: 'keyword', - }, - { - name: 'stats', - description: 'Stats groups', - example: 'group1', - type: 'keyword', - }, - { - name: 'search_type', - description: 'Search type', - example: 'QUERY_THEN_FETCH', - type: 'keyword', - }, - { - name: 'source_query', - description: 'Slow query', - example: '{"query":{"match_all":{"boost":1.0}}}', - type: 'keyword', - }, - { - name: 'extra_source', - description: 'Extra source information', - example: '', - type: 'keyword', - }, - { - name: 'total_hits', - description: 'Total hits', - example: 42, - type: 'keyword', - }, - { - name: 'total_shards', - description: 'Total queried shards', - example: 22, - type: 'keyword', - }, - { - name: 'routing', - description: 'Routing', - example: 's01HZ2QBk9jw4gtgaFtn', - type: 'keyword', - }, - { - name: 'id', - description: 'Id', - example: '', - type: 'keyword', - }, - { - name: 'type', - description: 'Type', - example: 'doc', - type: 'keyword', - }, - { - name: 'source', - description: 'Source of document that was indexed', - type: 'keyword', - }, - ], - }, - ], - }, - ], - }, - { - key: 'haproxy', - title: 'haproxy', - description: 'haproxy Module\n', - fields: [ - { - name: 'haproxy', - type: 'group', - description: '\n', - fields: [ - { - name: 'frontend_name', - description: - 'Name of the frontend (or listener) which received and processed the connection.', - }, - { - name: 'backend_name', - description: - 'Name of the backend (or listener) which was selected to manage the connection to the server.', - }, - { - name: 'server_name', - description: 'Name of the last server to which the connection was sent.', - }, - { - name: 'total_waiting_time_ms', - description: 'Total time in milliseconds spent waiting in the various queues', - type: 'long', - }, - { - name: 'connection_wait_time_ms', - description: - 'Total time in milliseconds spent waiting for the connection to establish to the final server', - type: 'long', - }, - { - name: 'bytes_read', - description: 'Total number of bytes transmitted to the client when the log is emitted.', - type: 'long', - }, - { - name: 'time_queue', - description: 'Total time in milliseconds spent waiting in the various queues.', - type: 'long', - }, - { - name: 'time_backend_connect', - description: - 'Total time in milliseconds spent waiting for the connection to establish to the final server, including retries.', - type: 'long', - }, - { - name: 'server_queue', - description: - 'Total number of requests which were processed before this one in the server queue.', - type: 'long', - }, - { - name: 'backend_queue', - description: - "Total number of requests which were processed before this one in the backend's global queue.", - type: 'long', - }, - { - name: 'bind_name', - description: 'Name of the listening address which received the connection.', - }, - { - name: 'error_message', - description: 'Error message logged by HAProxy in case of error.', - type: 'text', - }, - { - name: 'source', - type: 'keyword', - description: 'The HAProxy source of the log', - }, - { - name: 'termination_state', - description: 'Condition the session was in when the session ended.', - }, - { - name: 'mode', - type: 'keyword', - description: 'mode that the frontend is operating (TCP or HTTP)', - }, - { - name: 'connections', - description: 'Contains various counts of connections active in the process.', - type: 'group', - fields: [ - { - name: 'active', - description: - 'Total number of concurrent connections on the process when the session was logged.', - type: 'long', - }, - { - name: 'frontend', - description: - 'Total number of concurrent connections on the frontend when the session was logged.', - type: 'long', - }, - { - name: 'backend', - description: - 'Total number of concurrent connections handled by the backend when the session was logged.', - type: 'long', - }, - { - name: 'server', - description: - 'Total number of concurrent connections still active on the server when the session was logged.', - type: 'long', - }, - { - name: 'retries', - description: - 'Number of connection retries experienced by this session when trying to connect to the server.', - type: 'long', - }, - ], - }, - { - name: 'client', - description: 'Information about the client doing the request', - type: 'group', - fields: [ - { - name: 'ip', - type: 'alias', - path: 'source.address', - migration: true, - }, - { - name: 'port', - type: 'alias', - path: 'source.port', - migration: true, - }, - ], - }, - { - name: 'process_name', - type: 'alias', - path: 'process.name', - migration: true, - }, - { - name: 'pid', - type: 'alias', - path: 'process.pid', - migration: true, - }, - { - name: 'destination', - description: 'Destination information', - type: 'group', - fields: [ - { - name: 'port', - type: 'alias', - path: 'destination.port', - migration: true, - }, - { - name: 'ip', - type: 'alias', - path: 'destination.ip', - migration: true, - }, - ], - }, - { - name: 'geoip', - type: 'group', - description: - 'Contains GeoIP information gathered based on the client.ip field. Only present if the GeoIP Elasticsearch plugin is available and used.\n', - fields: [ - { - name: 'continent_name', - type: 'alias', - path: 'source.geo.continent_name', - migration: true, - }, - { - name: 'country_iso_code', - type: 'alias', - path: 'source.geo.country_iso_code', - migration: true, - }, - { - name: 'location', - type: 'alias', - path: 'source.geo.location', - migration: true, - }, - { - name: 'region_name', - type: 'alias', - path: 'source.geo.region_name', - migration: true, - }, - { - name: 'city_name', - type: 'alias', - path: 'source.geo.city_name', - migration: true, - }, - { - name: 'region_iso_code', - type: 'alias', - path: 'source.geo.region_iso_code', - migration: true, - }, - ], - }, - { - name: 'http', - description: 'Please add description', - type: 'group', - fields: [ - { - name: 'response', - description: 'Fields related to the HTTP response', - type: 'group', - fields: [ - { - name: 'captured_cookie', - description: - 'Optional "name=value" entry indicating that the client had this cookie in the response.\n', - }, - { - name: 'captured_headers', - description: - 'List of headers captured in the response due to the presence of the "capture response header" statement in the frontend.\n', - type: 'keyword', - }, - { - name: 'status_code', - type: 'alias', - path: 'http.response.status_code', - migration: true, - }, - ], - }, - { - name: 'request', - description: 'Fields related to the HTTP request', - type: 'group', - fields: [ - { - name: 'captured_cookie', - description: - 'Optional "name=value" entry indicating that the server has returned a cookie with its request.\n', - }, - { - name: 'captured_headers', - description: - 'List of headers captured in the request due to the presence of the "capture request header" statement in the frontend.\n', - type: 'keyword', - }, - { - name: 'raw_request_line', - description: - 'Complete HTTP request line, including the method, request and HTTP version string.', - type: 'keyword', - }, - { - name: 'time_wait_without_data_ms', - description: - 'Total time in milliseconds spent waiting for the server to send a full HTTP response, not counting data.', - type: 'long', - }, - { - name: 'time_wait_ms', - description: - 'Total time in milliseconds spent waiting for a full HTTP request from the client (not counting body) after the first byte was received.', - type: 'long', - }, - ], - }, - ], - }, - { - name: 'tcp', - description: 'TCP log format', - type: 'group', - fields: [ - { - name: 'connection_waiting_time_ms', - type: 'long', - description: - 'Total time in milliseconds elapsed between the accept and the last close', - }, - ], - }, - ], - }, - ], - }, - { - key: 'icinga', - title: 'Icinga', - description: 'Icinga Module\n', - fields: [ - { - name: 'icinga', - type: 'group', - description: '\n', - fields: [ - { - name: 'debug', - type: 'group', - description: 'Contains fields for the Icinga debug logs.\n', - fields: [ - { - name: 'facility', - type: 'keyword', - description: 'Specifies what component of Icinga logged the message.\n', - }, - { - name: 'severity', - type: 'alias', - path: 'log.level', - migration: true, - }, - { - name: 'message', - type: 'alias', - path: 'message', - migration: true, - }, - ], - }, - { - name: 'main', - type: 'group', - description: 'Contains fields for the Icinga main logs.\n', - fields: [ - { - name: 'facility', - type: 'keyword', - description: 'Specifies what component of Icinga logged the message.\n', - }, - { - name: 'severity', - type: 'alias', - path: 'log.level', - migration: true, - }, - { - name: 'message', - type: 'alias', - path: 'message', - migration: true, - }, - ], - }, - { - name: 'startup', - type: 'group', - description: 'Contains fields for the Icinga startup logs.\n', - fields: [ - { - name: 'facility', - type: 'keyword', - description: 'Specifies what component of Icinga logged the message.\n', - }, - { - name: 'severity', - type: 'alias', - path: 'log.level', - migration: true, - }, - { - name: 'message', - type: 'alias', - path: 'message', - migration: true, - }, - ], - }, - ], - }, - ], - }, - { - key: 'iis', - title: 'IIS', - description: 'Module for parsing IIS log files.\n', - fields: [ - { - name: 'iis', - type: 'group', - description: 'Fields from IIS log files.\n', - fields: [ - { - name: 'access', - type: 'group', - description: 'Contains fields for IIS access logs.\n', - fields: [ - { - name: 'sub_status', - type: 'long', - description: 'The HTTP substatus code.\n', - }, - { - name: 'win32_status', - type: 'long', - description: 'The Windows status code.\n', - }, - { - name: 'site_name', - type: 'keyword', - description: 'The site name and instance number.\n', - }, - { - name: 'server_name', - type: 'keyword', - description: 'The name of the server on which the log file entry was generated.\n', - }, - { - name: 'cookie', - type: 'keyword', - description: 'The content of the cookie sent or received, if any.\n', - }, - { - name: 'body_received.bytes', - type: 'alias', - path: 'http.request.body.bytes', - migration: true, - }, - { - name: 'body_sent.bytes', - type: 'alias', - path: 'http.response.body.bytes', - migration: true, - }, - { - name: 'server_ip', - type: 'alias', - path: 'destination.address', - migration: true, - }, - { - name: 'method', - type: 'alias', - path: 'http.request.method', - migration: true, - }, - { - name: 'url', - type: 'alias', - path: 'url.path', - migration: true, - }, - { - name: 'query_string', - type: 'alias', - path: 'url.query', - migration: true, - }, - { - name: 'port', - type: 'alias', - path: 'destination.port', - migration: true, - }, - { - name: 'user_name', - type: 'alias', - path: 'user.name', - migration: true, - }, - { - name: 'remote_ip', - type: 'alias', - path: 'source.address', - migration: true, - }, - { - name: 'referrer', - type: 'alias', - path: 'http.request.referrer', - migration: true, - }, - { - name: 'response_code', - type: 'alias', - path: 'http.response.status_code', - migration: true, - }, - { - name: 'http_version', - type: 'alias', - path: 'http.version', - migration: true, - }, - { - name: 'hostname', - type: 'alias', - path: 'host.hostname', - migration: true, - }, - { - name: 'user_agent', - type: 'group', - fields: [ - { - name: 'device', - type: 'alias', - path: 'user_agent.device.name', - migration: true, - }, - { - name: 'name', - type: 'alias', - path: 'user_agent.name', - migration: true, - }, - { - name: 'os', - type: 'alias', - path: 'user_agent.os.full_name', - migration: true, - }, - { - name: 'os_name', - type: 'alias', - path: 'user_agent.os.name', - migration: true, - }, - { - name: 'original', - type: 'alias', - path: 'user_agent.original', - migration: true, - }, - ], - }, - { - name: 'geoip', - type: 'group', - fields: [ - { - name: 'continent_name', - type: 'alias', - path: 'source.geo.continent_name', - migration: true, - }, - { - name: 'country_iso_code', - type: 'alias', - path: 'source.geo.country_iso_code', - migration: true, - }, - { - name: 'location', - type: 'alias', - path: 'source.geo.location', - migration: true, - }, - { - name: 'region_name', - type: 'alias', - path: 'source.geo.region_name', - migration: true, - }, - { - name: 'city_name', - type: 'alias', - path: 'source.geo.city_name', - migration: true, - }, - { - name: 'region_iso_code', - type: 'alias', - path: 'source.geo.region_iso_code', - migration: true, - }, - ], - }, - ], - }, - { - name: 'error', - type: 'group', - description: 'Contains fields for IIS error logs.\n', - fields: [ - { - name: 'reason_phrase', - type: 'keyword', - description: 'The HTTP reason phrase.\n', - }, - { - name: 'queue_name', - type: 'keyword', - description: 'The IIS application pool name.\n', - }, - { - name: 'remote_ip', - type: 'alias', - path: 'source.address', - migration: true, - }, - { - name: 'remote_port', - type: 'alias', - path: 'source.port', - migration: true, - }, - { - name: 'server_ip', - type: 'alias', - path: 'destination.address', - migration: true, - }, - { - name: 'server_port', - type: 'alias', - path: 'destination.port', - migration: true, - }, - { - name: 'http_version', - type: 'alias', - path: 'http.version', - migration: true, - }, - { - name: 'method', - type: 'alias', - path: 'http.request.method', - migration: true, - }, - { - name: 'url', - type: 'alias', - path: 'url.original', - migration: true, - }, - { - name: 'response_code', - type: 'alias', - path: 'http.response.status_code', - migration: true, - }, - { - name: 'geoip', - type: 'group', - fields: [ - { - name: 'continent_name', - type: 'alias', - path: 'source.geo.continent_name', - migration: true, - }, - { - name: 'country_iso_code', - type: 'alias', - path: 'source.geo.country_iso_code', - migration: true, - }, - { - name: 'location', - type: 'alias', - path: 'source.geo.location', - migration: true, - }, - { - name: 'region_name', - type: 'alias', - path: 'source.geo.region_name', - migration: true, - }, - { - name: 'city_name', - type: 'alias', - path: 'source.geo.city_name', - migration: true, - }, - { - name: 'region_iso_code', - type: 'alias', - path: 'source.geo.region_iso_code', - migration: true, - }, - ], - }, - ], - }, - ], - }, - ], - }, - { - key: 'kafka', - title: 'Kafka', - description: 'Kafka module\n', - fields: [ - { - name: 'kafka', - type: 'group', - description: '\n', - fields: [ - { - name: 'log', - type: 'group', - description: 'Kafka log lines.\n', - fields: [ - { - name: 'level', - type: 'alias', - path: 'log.level', - migration: true, - }, - { - name: 'message', - type: 'alias', - path: 'message', - migration: true, - }, - { - name: 'component', - type: 'keyword', - description: 'Component the log is coming from.\n', - }, - { - name: 'class', - type: 'keyword', - description: 'Java class the log is coming from.\n', - }, - { - name: 'trace', - type: 'group', - description: 'Trace in the log line.\n', - fields: [ - { - name: 'class', - type: 'keyword', - description: 'Java class the trace is coming from.\n', - }, - { - name: 'message', - type: 'text', - description: 'Message part of the trace.\n', - }, - ], - }, - ], - }, - ], - }, - ], - }, - { - key: 'kibana', - title: 'kibana', - description: 'kibana Module\n', - fields: [ - { - name: 'kibana', - type: 'group', - description: '\n', - fields: [ - { - name: 'log', - type: 'group', - description: 'Kafka log lines.\n', - fields: [ - { - name: 'tags', - type: 'keyword', - description: 'Kibana logging tags.\n', - }, - { - name: 'state', - type: 'keyword', - description: 'Current state of Kibana.\n', - }, - { - name: 'meta', - type: 'object', - object_type: 'keyword', - }, - { - name: 'kibana.log.meta.req.headers.referer', - type: 'alias', - path: 'http.request.referrer', - migration: true, - }, - { - name: 'kibana.log.meta.req.referer', - type: 'alias', - path: 'http.request.referrer', - migration: true, - }, - { - name: 'kibana.log.meta.req.headers.user-agent', - type: 'alias', - path: 'user_agent.original', - migration: true, - }, - { - name: 'kibana.log.meta.req.remoteAddress', - type: 'alias', - path: 'source.address', - migration: true, - }, - { - name: 'kibana.log.meta.req.url', - type: 'alias', - path: 'url.original', - migration: true, - }, - { - name: 'kibana.log.meta.statusCode', - type: 'alias', - path: 'http.response.status_code', - migration: true, - }, - { - name: 'kibana.log.meta.method', - type: 'alias', - path: 'http.request.method', - migration: true, - }, - ], - }, - ], - }, - ], - }, - { - key: 'logstash', - title: 'logstash', - description: 'logstash Module\n', - fields: [ - { - name: 'logstash', - type: 'group', - description: '\n', - fields: [ - { - name: 'log', - title: 'Logstash', - type: 'group', - description: 'Fields from the Logstash logs.\n', - fields: [ - { - name: 'module', - type: 'keyword', - description: 'The module or class where the event originate.\n', - }, - { - name: 'thread', - type: 'keyword', - description: 'Information about the running thread where the log originate.\n', - multi_fields: [ - { - name: 'text', - type: 'text', - }, - ], - }, - { - name: 'log_event', - type: 'object', - description: 'key and value debugging information.\n', - }, - { - name: 'pipeline_id', - type: 'keyword', - example: 'main', - description: 'The ID of the pipeline.\n', - }, - { - name: 'message', - type: 'alias', - path: 'message', - migration: true, - }, - { - name: 'level', - type: 'alias', - path: 'log.level', - migration: true, - }, - ], - }, - { - name: 'slowlog', - type: 'group', - description: 'slowlog\n', - fields: [ - { - name: 'module', - type: 'keyword', - description: 'The module or class where the event originate.\n', - }, - { - name: 'thread', - type: 'keyword', - description: 'Information about the running thread where the log originate.\n', - multi_fields: [ - { - name: 'text', - type: 'text', - }, - ], - }, - { - name: 'event', - type: 'keyword', - description: 'Raw dump of the original event\n', - multi_fields: [ - { - name: 'text', - type: 'text', - }, - ], - }, - { - name: 'plugin_name', - type: 'keyword', - description: 'Name of the plugin\n', - }, - { - name: 'plugin_type', - type: 'keyword', - description: 'Type of the plugin: Inputs, Filters, Outputs or Codecs.\n', - }, - { - name: 'took_in_millis', - type: 'long', - description: 'Execution time for the plugin in milliseconds.\n', - }, - { - name: 'plugin_params', - type: 'keyword', - description: 'String value of the plugin configuration\n', - multi_fields: [ - { - name: 'text', - type: 'text', - }, - ], - }, - { - name: 'plugin_params_object', - type: 'object', - description: 'key -> value of the configuration used by the plugin.\n', - }, - { - name: 'level', - type: 'alias', - path: 'log.level', - migration: true, - }, - { - name: 'took_in_nanos', - type: 'alias', - path: 'event.duration', - migration: true, - }, - ], - }, - ], - }, - ], - }, - { - key: 'mongodb', - title: 'mongodb', - description: 'Module for parsing MongoDB log files.\n', - fields: [ - { - name: 'mongodb', - type: 'group', - description: 'Fields from MongoDB logs.\n', - fields: [ - { - name: 'log', - type: 'group', - description: 'Contains fields from MongoDB logs.\n', - fields: [ - { - name: 'component', - description: 'Functional categorization of message\n', - example: 'COMMAND', - type: 'keyword', - }, - { - name: 'context', - description: 'Context of message\n', - example: 'initandlisten', - type: 'keyword', - }, - { - name: 'severity', - type: 'alias', - path: 'log.level', - migration: true, - }, - { - name: 'message', - type: 'alias', - path: 'message', - migration: true, - }, - ], - }, - ], - }, - ], - }, - { - key: 'mysql', - title: 'MySQL', - description: 'Module for parsing the MySQL log files.\n', - short_config: true, - fields: [ - { - name: 'mysql', - type: 'group', - description: 'Fields from the MySQL log files.\n', - fields: [ - { - name: 'thread_id', - type: 'long', - description: 'The connection or thread ID for the query.\n', - }, - { - name: 'error', - type: 'group', - description: 'Contains fields from the MySQL error logs.\n', - fields: [ - { - name: 'thread_id', - type: 'alias', - path: 'mysql.thread_id', - migration: true, - }, - { - name: 'level', - type: 'alias', - path: 'log.level', - migration: true, - }, - { - name: 'message', - type: 'alias', - path: 'message', - migration: true, - }, - ], - }, - { - name: 'slowlog', - type: 'group', - description: 'Contains fields from the MySQL slow logs.\n', - fields: [ - { - name: 'lock_time.sec', - type: 'float', - description: - 'The amount of time the query waited for the lock to be available. The value is in seconds, as a floating point number.\n', - }, - { - name: 'rows_sent', - type: 'long', - description: 'The number of rows returned by the query.\n', - }, - { - name: 'rows_examined', - type: 'long', - description: 'The number of rows scanned by the query.\n', - }, - { - name: 'rows_affected', - type: 'long', - description: 'The number of rows modified by the query.\n', - }, - { - name: 'bytes_sent', - type: 'long', - format: 'bytes', - description: 'The number of bytes sent to client.\n', - }, - { - name: 'bytes_received', - type: 'long', - format: 'bytes', - description: 'The number of bytes received from client.\n', - }, - { - name: 'query', - description: 'The slow query.\n', - }, - { - name: 'id', - type: 'alias', - path: 'mysql.thread_id', - migration: true, - }, - { - name: 'schema', - type: 'keyword', - description: 'The schema where the slow query was executed.\n', - }, - { - name: 'current_user', - type: 'keyword', - description: - 'Current authenticated user, used to determine access privileges. Can differ from the value for user.\n', - }, - { - name: 'last_errno', - type: 'keyword', - description: 'Last SQL error seen.\n', - }, - { - name: 'killed', - type: 'keyword', - description: 'Code of the reason if the query was killed.\n', - }, - { - name: 'query_cache_hit', - type: 'boolean', - description: 'Whether the query cache was hit.\n', - }, - { - name: 'tmp_table', - type: 'boolean', - description: 'Whether a temporary table was used to resolve the query.\n', - }, - { - name: 'tmp_table_on_disk', - type: 'boolean', - description: 'Whether the query needed temporary tables on disk.\n', - }, - { - name: 'tmp_tables', - type: 'long', - description: 'Number of temporary tables created for this query\n', - }, - { - name: 'tmp_disk_tables', - type: 'long', - description: 'Number of temporary tables created on disk for this query.\n', - }, - { - name: 'tmp_table_sizes', - type: 'long', - format: 'bytes', - description: 'Size of temporary tables created for this query.', - }, - { - name: 'filesort', - type: 'boolean', - description: 'Whether filesort optimization was used.\n', - }, - { - name: 'filesort_on_disk', - type: 'boolean', - description: - 'Whether filesort optimization was used and it needed temporary tables on disk.\n', - }, - { - name: 'priority_queue', - type: 'boolean', - description: 'Whether a priority queue was used for filesort.\n', - }, - { - name: 'full_scan', - type: 'boolean', - description: 'Whether a full table scan was needed for the slow query.\n', - }, - { - name: 'full_join', - type: 'boolean', - description: - 'Whether a full join was needed for the slow query (no indexes were used for joins).\n', - }, - { - name: 'merge_passes', - type: 'long', - description: 'Number of merge passes executed for the query.\n', - }, - { - name: 'sort_merge_passes', - type: 'long', - description: 'Number of merge passes that the sort algorithm has had to do.\n', - }, - { - name: 'sort_range_count', - type: 'long', - description: 'Number of sorts that were done using ranges.\n', - }, - { - name: 'sort_rows', - type: 'long', - description: 'Number of sorted rows.\n', - }, - { - name: 'sort_scan_count', - type: 'long', - description: 'Number of sorts that were done by scanning the table.\n', - }, - { - name: 'log_slow_rate_type', - type: 'keyword', - description: - 'Type of slow log rate limit, it can be `session` if the rate limit is applied per session, or `query` if it applies per query.\n', - }, - { - name: 'log_slow_rate_limit', - type: 'keyword', - description: - 'Slow log rate limit, a value of 100 means that one in a hundred queries or sessions are being logged.\n', - }, - { - name: 'read_first', - type: 'long', - description: 'The number of times the first entry in an index was read.\n', - }, - { - name: 'read_last', - type: 'long', - description: 'The number of times the last key in an index was read.\n', - }, - { - name: 'read_key', - type: 'long', - description: 'The number of requests to read a row based on a key.\n', - }, - { - name: 'read_next', - type: 'long', - description: 'The number of requests to read the next row in key order.\n', - }, - { - name: 'read_prev', - type: 'long', - description: 'The number of requests to read the previous row in key order.\n', - }, - { - name: 'read_rnd', - type: 'long', - description: 'The number of requests to read a row based on a fixed position.\n', - }, - { - name: 'read_rnd_next', - type: 'long', - description: 'The number of requests to read the next row in the data file.\n', - }, - { - name: 'innodb', - type: 'group', - description: 'Contains fields relative to InnoDB engine\n', - fields: [ - { - name: 'trx_id', - type: 'keyword', - description: 'Transaction ID\n', - }, - { - name: 'io_r_ops', - type: 'long', - description: 'Number of page read operations.\n', - }, - { - name: 'io_r_bytes', - type: 'long', - format: 'bytes', - description: 'Bytes read during page read operations.\n', - }, - { - name: 'io_r_wait.sec', - type: 'long', - description: 'How long it took to read all needed data from storage.\n', - }, - { - name: 'rec_lock_wait.sec', - type: 'long', - description: 'How long the query waited for locks.\n', - }, - { - name: 'queue_wait.sec', - type: 'long', - description: - 'How long the query waited to enter the InnoDB queue and to be executed once in the queue.\n', - }, - { - name: 'pages_distinct', - type: 'long', - description: 'Approximated count of pages accessed to execute the query.\n', - }, - ], - }, - { - name: 'user', - type: 'alias', - path: 'user.name', - migration: true, - }, - { - name: 'host', - type: 'alias', - path: 'source.domain', - migration: true, - }, - { - name: 'ip', - type: 'alias', - path: 'source.ip', - migration: true, - }, - ], - }, - ], - }, - ], - }, - { - key: 'nats', - title: 'nats', - description: 'Module for parsing NATS log files.\n', - release: 'beta', - fields: [ - { - name: 'nats', - type: 'group', - description: 'Fields from NATS logs.\n', - fields: [ - { - name: 'log', - type: 'group', - description: 'Nats log files\n', - release: 'beta', - fields: [ - { - name: 'client', - type: 'group', - description: 'Fields from NATS logs client.\n', - fields: [ - { - name: 'id', - type: 'integer', - description: 'The id of the client\n', - }, - ], - }, - { - name: 'msg', - type: 'group', - description: 'Fields from NATS logs message.\n', - fields: [ - { - name: 'bytes', - type: 'long', - format: 'bytes', - description: 'Size of the payload in bytes\n', - }, - { - name: 'type', - type: 'keyword', - description: 'The protocol message type\n', - }, - { - name: 'subject', - type: 'keyword', - description: 'Subject name this message was received on\n', - }, - { - name: 'sid', - type: 'integer', - description: 'The unique alphanumeric subscription ID of the subject\n', - }, - { - name: 'reply_to', - type: 'keyword', - description: - 'The inbox subject on which the publisher is listening for responses\n', - }, - { - name: 'max_messages', - type: 'integer', - description: - 'An optional number of messages to wait for before automatically unsubscribing\n', - }, - { - name: 'error.message', - type: 'text', - description: 'Details about the error occurred\n', - }, - { - name: 'queue_group', - type: 'text', - description: 'The queue group which subscriber will join\n', - }, - ], - }, - ], - }, - ], - }, - ], - }, - { - key: 'nginx', - title: 'Nginx', - description: 'Module for parsing the Nginx log files.\n', - short_config: true, - fields: [ - { - name: 'nginx', - type: 'group', - description: 'Fields from the Nginx log files.\n', - fields: [ - { - name: 'access', - type: 'group', - description: 'Contains fields for the Nginx access logs.\n', - fields: [ - { - name: 'remote_ip_list', - type: 'array', - description: - 'An array of remote IP addresses. It is a list because it is common to include, besides the client IP address, IP addresses from headers like `X-Forwarded-For`. Real source IP is restored to `source.ip`.\n', - }, - { - name: 'body_sent.bytes', - type: 'alias', - path: 'http.response.body.bytes', - migration: true, - }, - { - name: 'user_name', - type: 'alias', - path: 'user.name', - migration: true, - }, - { - name: 'method', - type: 'alias', - path: 'http.request.method', - migration: true, - }, - { - name: 'url', - type: 'alias', - path: 'url.original', - migration: true, - }, - { - name: 'http_version', - type: 'alias', - path: 'http.version', - migration: true, - }, - { - name: 'response_code', - type: 'alias', - path: 'http.response.status_code', - migration: true, - }, - { - name: 'referrer', - type: 'alias', - path: 'http.request.referrer', - migration: true, - }, - { - name: 'agent', - type: 'alias', - path: 'user_agent.original', - migration: true, - }, - { - name: 'user_agent', - type: 'group', - fields: [ - { - name: 'device', - type: 'alias', - path: 'user_agent.device.name', - migration: true, - }, - { - name: 'name', - type: 'alias', - path: 'user_agent.name', - migration: true, - }, - { - name: 'os', - type: 'alias', - path: 'user_agent.os.full_name', - migration: true, - }, - { - name: 'os_name', - type: 'alias', - path: 'user_agent.os.name', - migration: true, - }, - { - name: 'original', - type: 'alias', - path: 'user_agent.original', - migration: true, - }, - ], - }, - { - name: 'geoip', - type: 'group', - fields: [ - { - name: 'continent_name', - type: 'alias', - path: 'source.geo.continent_name', - migration: true, - }, - { - name: 'country_iso_code', - type: 'alias', - path: 'source.geo.country_iso_code', - migration: true, - }, - { - name: 'location', - type: 'alias', - path: 'source.geo.location', - migration: true, - }, - { - name: 'region_name', - type: 'alias', - path: 'source.geo.region_name', - migration: true, - }, - { - name: 'city_name', - type: 'alias', - path: 'source.geo.city_name', - migration: true, - }, - { - name: 'region_iso_code', - type: 'alias', - path: 'source.geo.region_iso_code', - migration: true, - }, - ], - }, - ], - }, - { - name: 'error', - type: 'group', - description: 'Contains fields for the Nginx error logs.\n', - fields: [ - { - name: 'connection_id', - type: 'long', - description: 'Connection identifier.\n', - }, - { - name: 'level', - type: 'alias', - path: 'log.level', - migration: true, - }, - { - name: 'pid', - type: 'alias', - path: 'process.pid', - migration: true, - }, - { - name: 'tid', - type: 'alias', - path: 'process.thread.id', - migration: true, - }, - { - name: 'message', - type: 'alias', - path: 'message', - migration: true, - }, - ], - }, - { - name: 'ingress_controller', - type: 'group', - description: 'Contains fields for the Ingress Nginx controller access logs.\n', - fields: [ - { - name: 'remote_ip_list', - type: 'array', - description: - 'An array of remote IP addresses. It is a list because it is common to include, besides the client IP address, IP addresses from headers like `X-Forwarded-For`. Real source IP is restored to `source.ip`.\n', - }, - { - name: 'http.request.length', - type: 'long', - format: 'bytes', - description: - 'The request length (including request line, header, and request body)\n', - }, - { - name: 'http.request.time', - type: 'double', - format: 'duration', - description: 'Time elapsed since the first bytes were read from the client\n', - }, - { - name: 'upstream.name', - type: 'text', - description: 'The name of the upstream.\n', - }, - { - name: 'upstream.alternative_name', - type: 'text', - description: 'The name of the alternative upstream.\n', - }, - { - name: 'upstream.response.length', - type: 'long', - format: 'bytes', - description: 'The length of the response obtained from the upstream server\n', - }, - { - name: 'upstream.response.time', - type: 'double', - format: 'duration', - description: - 'The time spent on receiving the response from the upstream server as seconds with millisecond resolution\n', - }, - { - name: 'upstream.response.status_code', - type: 'long', - description: 'The status code of the response obtained from the upstream server\n', - }, - { - name: 'http.request.id', - type: 'text', - description: 'The randomly generated ID of the request\n', - }, - { - name: 'upstream.ip', - type: 'ip', - description: - 'The IP address of the upstream server. If several servers were contacted during request processing, their addresses are separated by commas.\n', - }, - { - name: 'upstream.port', - type: 'long', - description: 'The port of the upstream server.\n', - }, - { - name: 'body_sent.bytes', - type: 'alias', - path: 'http.response.body.bytes', - migration: true, - }, - { - name: 'user_name', - type: 'alias', - path: 'user.name', - migration: true, - }, - { - name: 'method', - type: 'alias', - path: 'http.request.method', - migration: true, - }, - { - name: 'url', - type: 'alias', - path: 'url.original', - migration: true, - }, - { - name: 'http_version', - type: 'alias', - path: 'http.version', - migration: true, - }, - { - name: 'response_code', - type: 'alias', - path: 'http.response.status_code', - migration: true, - }, - { - name: 'referrer', - type: 'alias', - path: 'http.request.referrer', - migration: true, - }, - { - name: 'agent', - type: 'alias', - path: 'user_agent.original', - migration: true, - }, - { - name: 'user_agent', - type: 'group', - fields: [ - { - name: 'device', - type: 'alias', - path: 'user_agent.device.name', - migration: true, - }, - { - name: 'name', - type: 'alias', - path: 'user_agent.name', - migration: true, - }, - { - name: 'os', - type: 'alias', - path: 'user_agent.os.full_name', - migration: true, - }, - { - name: 'os_name', - type: 'alias', - path: 'user_agent.os.name', - migration: true, - }, - { - name: 'original', - type: 'alias', - path: 'user_agent.original', - migration: true, - }, - ], - }, - { - name: 'geoip', - type: 'group', - fields: [ - { - name: 'continent_name', - type: 'alias', - path: 'source.geo.continent_name', - migration: true, - }, - { - name: 'country_iso_code', - type: 'alias', - path: 'source.geo.country_iso_code', - migration: true, - }, - { - name: 'location', - type: 'alias', - path: 'source.geo.location', - migration: true, - }, - { - name: 'region_name', - type: 'alias', - path: 'source.geo.region_name', - migration: true, - }, - { - name: 'city_name', - type: 'alias', - path: 'source.geo.city_name', - migration: true, - }, - { - name: 'region_iso_code', - type: 'alias', - path: 'source.geo.region_iso_code', - migration: true, - }, - ], - }, - ], - }, - ], - }, - ], - }, - { - key: 'osquery', - title: 'Osquery', - description: 'Fields exported by the `osquery` module\n', - fields: [ - { - name: 'osquery', - type: 'group', - description: '\n', - fields: [ - { - name: 'result', - type: 'group', - description: 'Common fields exported by the result metricset.\n', - fields: [ - { - name: 'name', - type: 'keyword', - description: 'The name of the query that generated this event.\n', - }, - { - name: 'action', - type: 'keyword', - description: - 'For incremental data, marks whether the entry was added or removed. It can be one of "added", "removed", or "snapshot".\n', - }, - { - name: 'host_identifier', - type: 'keyword', - description: - 'The identifier for the host on which the osquery agent is running. Normally the hostname.\n', - }, - { - name: 'unix_time', - type: 'long', - description: - 'Unix timestamp of the event, in seconds since the epoch. Used for computing the `@timestamp` column.\n', - }, - { - name: 'calendar_time', - type: 'keyword', - description: - 'String representation of the collection time, as formatted by osquery.\n', - }, - ], - }, - ], - }, - ], - }, - { - key: 'postgresql', - title: 'PostgreSQL', - description: 'Module for parsing the PostgreSQL log files.\n', - short_config: true, - fields: [ - { - name: 'postgresql', - type: 'group', - description: 'Fields from PostgreSQL logs.\n', - fields: [ - { - name: 'log', - type: 'group', - description: 'Fields from the PostgreSQL log files.\n', - fields: [ - { - name: 'timestamp', - deprecated: '7.3.0', - description: 'The timestamp from the log line.\n', - }, - { - name: 'core_id', - type: 'long', - description: 'Core id\n', - }, - { - name: 'database', - example: 'mydb', - description: 'Name of database\n', - }, - { - name: 'query', - example: 'SELECT * FROM users;', - description: 'Query statement.\n', - }, - { - name: 'query_step', - example: 'parse', - description: - 'Statement step when using extended query protocol (one of statement, parse, bind or execute)\n', - }, - { - name: 'query_name', - example: 'pdo_stmt_00000001', - description: - 'Name given to a query when using extended query protocol. If it is "", or not present, this field is ignored.\n', - }, - { - name: 'error.code', - type: 'long', - description: 'Error code returned by Postgres (if any)', - }, - { - name: 'timezone', - type: 'alias', - path: 'event.timezone', - migration: true, - }, - { - name: 'thread_id', - type: 'alias', - path: 'process.pid', - migration: true, - }, - { - name: 'user', - type: 'alias', - path: 'user.name', - migration: true, - }, - { - name: 'level', - type: 'alias', - path: 'log.level', - migration: true, - }, - { - name: 'message', - type: 'alias', - path: 'message', - migration: true, - }, - ], - }, - ], - }, - ], - }, - { - key: 'redis', - title: 'Redis', - description: 'Redis Module\n', - fields: [ - { - name: 'redis', - type: 'group', - description: '\n', - fields: [ - { - name: 'log', - type: 'group', - description: 'Redis log files\n', - fields: [ - { - name: 'role', - type: 'keyword', - description: - 'The role of the Redis instance. Can be one of `master`, `slave`, `child` (for RDF/AOF writing child), or `sentinel`.\n', - }, - { - name: 'pid', - type: 'alias', - path: 'process.pid', - migration: true, - }, - { - name: 'level', - type: 'alias', - path: 'log.level', - migration: true, - }, - { - name: 'message', - type: 'alias', - path: 'message', - migration: true, - }, - ], - }, - { - name: 'slowlog', - type: 'group', - description: 'Slow logs are retrieved from Redis via a network connection.\n', - fields: [ - { - name: 'cmd', - type: 'keyword', - description: 'The command executed.\n', - }, - { - name: 'duration.us', - type: 'long', - description: 'How long it took to execute the command in microseconds.\n', - }, - { - name: 'id', - type: 'long', - description: 'The ID of the query.\n', - }, - { - name: 'key', - type: 'keyword', - description: 'The key on which the command was executed.\n', - }, - { - name: 'args', - type: 'keyword', - description: 'The arguments with which the command was called.\n', - }, - ], - }, - ], - }, - ], - }, - { - key: 'santa', - title: 'Google Santa', - description: 'Santa Module\n', - fields: [ - { - name: 'santa', - type: 'group', - description: '\n', - fields: [ - { - name: 'action', - type: 'keyword', - example: 'EXEC', - description: 'Action', - }, - { - name: 'decision', - type: 'keyword', - example: 'ALLOW', - description: 'Decision that santad took.', - }, - { - name: 'reason', - type: 'keyword', - example: 'CERT', - description: 'Reason for the decsision.', - }, - { - name: 'mode', - type: 'keyword', - example: 'M', - description: 'Operating mode of Santa.', - }, - { - name: 'disk', - type: 'group', - description: 'Fields for DISKAPPEAR actions.', - fields: [ - { - name: 'volume', - description: 'The volume name.', - }, - { - name: 'bus', - description: 'The disk bus protocol.', - }, - { - name: 'serial', - description: 'The disk serial number.', - }, - { - name: 'bsdname', - example: 'disk1s3', - description: 'The disk BSD name.', - }, - { - name: 'model', - example: 'APPLE SSD SM0512L', - description: 'The disk model.', - }, - { - name: 'fs', - example: 'apfs', - description: 'The disk volume kind (filesystem type).', - }, - { - name: 'mount', - description: 'The disk volume path.', - }, - ], - }, - ], - }, - { - name: 'certificate.common_name', - type: 'keyword', - description: 'Common name from code signing certificate.', - }, - { - name: 'certificate.sha256', - type: 'keyword', - description: 'SHA256 hash of code signing certificate.', - }, - ], - }, - { - key: 'system', - title: 'System', - description: 'Module for parsing system log files.\n', - short_config: true, - fields: [ - { - name: 'system', - type: 'group', - description: 'Fields from the system log files.\n', - fields: [ - { - name: 'auth', - type: 'group', - description: 'Fields from the Linux authorization logs.\n', - fields: [ - { - name: 'timestamp', - type: 'alias', - path: '@timestamp', - migration: true, - }, - { - name: 'hostname', - type: 'alias', - path: 'host.hostname', - migration: true, - }, - { - name: 'program', - type: 'alias', - path: 'process.name', - migration: true, - }, - { - name: 'pid', - type: 'alias', - path: 'process.pid', - migration: true, - }, - { - name: 'message', - type: 'alias', - path: 'message', - migration: true, - }, - { - name: 'user', - type: 'alias', - path: 'user.name', - migration: true, - }, - { - name: 'ssh', - type: 'group', - fields: [ - { - name: 'method', - description: - 'The SSH authentication method. Can be one of "password" or "publickey".\n', - }, - { - name: 'signature', - description: 'The signature of the client public key.\n', - }, - { - name: 'dropped_ip', - type: 'ip', - description: - 'The client IP from SSH connections that are open and immediately dropped.\n', - }, - { - name: 'event', - example: 'Accepted', - description: - 'The SSH event as found in the logs (Accepted, Invalid, Failed, etc.)\n', - }, - { - name: 'ip', - type: 'alias', - path: 'source.ip', - migration: true, - }, - { - name: 'port', - type: 'alias', - path: 'source.port', - migration: true, - }, - { - name: 'geoip', - type: 'group', - fields: [ - { - name: 'continent_name', - type: 'alias', - path: 'source.geo.continent_name', - migration: true, - }, - { - name: 'country_iso_code', - type: 'alias', - path: 'source.geo.country_iso_code', - migration: true, - }, - { - name: 'location', - type: 'alias', - path: 'source.geo.location', - migration: true, - }, - { - name: 'region_name', - type: 'alias', - path: 'source.geo.region_name', - migration: true, - }, - { - name: 'city_name', - type: 'alias', - path: 'source.geo.city_name', - migration: true, - }, - { - name: 'region_iso_code', - type: 'alias', - path: 'source.geo.region_iso_code', - migration: true, - }, - ], - }, - ], - }, - { - name: 'sudo', - type: 'group', - description: 'Fields specific to events created by the `sudo` command.\n', - fields: [ - { - name: 'error', - example: 'user NOT in sudoers', - description: 'The error message in case the sudo command failed.\n', - }, - { - name: 'tty', - description: 'The TTY where the sudo command is executed.\n', - }, - { - name: 'pwd', - description: 'The current directory where the sudo command is executed.\n', - }, - { - name: 'user', - example: 'root', - description: 'The target user to which the sudo command is switching.\n', - }, - { - name: 'command', - description: 'The command executed via sudo.\n', - }, - ], - }, - { - name: 'useradd', - type: 'group', - description: 'Fields specific to events created by the `useradd` command.\n', - fields: [ - { - name: 'home', - description: 'The home folder for the new user.', - }, - { - name: 'shell', - description: 'The default shell for the new user.', - }, - { - name: 'name', - type: 'alias', - path: 'user.name', - migration: true, - }, - { - name: 'uid', - type: 'alias', - path: 'user.id', - migration: true, - }, - { - name: 'gid', - type: 'alias', - path: 'group.id', - migration: true, - }, - ], - }, - { - name: 'groupadd', - type: 'group', - description: 'Fields specific to events created by the `groupadd` command.\n', - fields: [ - { - name: 'name', - type: 'alias', - path: 'group.name', - migration: true, - }, - { - name: 'gid', - type: 'alias', - path: 'group.id', - migration: true, - }, - ], - }, - ], - }, - { - name: 'syslog', - type: 'group', - description: 'Contains fields from the syslog system logs.\n', - fields: [ - { - name: 'timestamp', - type: 'alias', - path: '@timestamp', - migration: true, - }, - { - name: 'hostname', - type: 'alias', - path: 'host.hostname', - migration: true, - }, - { - name: 'program', - type: 'alias', - path: 'process.name', - migration: true, - }, - { - name: 'pid', - type: 'alias', - path: 'process.pid', - migration: true, - }, - { - name: 'message', - type: 'alias', - path: 'message', - migration: true, - }, - ], - }, - ], - }, - ], - }, - { - key: 'traefik', - title: 'Traefik', - description: 'Module for parsing the Traefik log files.\n', - fields: [ - { - name: 'traefik', - type: 'group', - description: 'Fields from the Traefik log files.\n', - fields: [ - { - name: 'access', - type: 'group', - description: 'Contains fields for the Traefik access logs.\n', - fields: [ - { - name: 'user_identifier', - type: 'keyword', - description: 'Is the RFC 1413 identity of the client\n', - }, - { - name: 'request_count', - type: 'long', - description: 'The number of requests\n', - }, - { - name: 'frontend_name', - type: 'keyword', - description: 'The name of the frontend used\n', - }, - { - name: 'backend_url', - type: 'keyword', - description: 'The url of the backend where request is forwarded', - }, - { - name: 'body_sent.bytes', - type: 'alias', - path: 'http.response.body.bytes', - migration: true, - }, - { - name: 'remote_ip', - type: 'alias', - path: 'source.address', - migration: true, - }, - { - name: 'user_name', - type: 'alias', - path: 'user.name', - migration: true, - }, - { - name: 'method', - type: 'alias', - path: 'http.request.method', - migration: true, - }, - { - name: 'url', - type: 'alias', - path: 'url.original', - migration: true, - }, - { - name: 'http_version', - type: 'alias', - path: 'http.version', - migration: true, - }, - { - name: 'response_code', - type: 'alias', - path: 'http.response.status_code', - migration: true, - }, - { - name: 'referrer', - type: 'alias', - path: 'http.request.referrer', - migration: true, - }, - { - name: 'agent', - type: 'alias', - path: 'user_agent.original', - migration: true, - }, - { - name: 'user_agent', - type: 'group', - fields: [ - { - name: 'device', - type: 'alias', - path: 'user_agent.device.name', - }, - { - name: 'name', - type: 'alias', - path: 'user_agent.name', - }, - { - name: 'os', - type: 'alias', - path: 'user_agent.os.full_name', - }, - { - name: 'os_name', - type: 'alias', - path: 'user_agent.os.name', - }, - { - name: 'original', - type: 'alias', - path: 'user_agent.original', - }, - ], - }, - { - name: 'geoip', - type: 'group', - fields: [ - { - name: 'continent_name', - type: 'alias', - path: 'source.geo.continent_name', - }, - { - name: 'country_iso_code', - type: 'alias', - path: 'source.geo.country_iso_code', - }, - { - name: 'location', - type: 'alias', - path: 'source.geo.location', - }, - { - name: 'region_name', - type: 'alias', - path: 'source.geo.region_name', - }, - { - name: 'city_name', - type: 'alias', - path: 'source.geo.city_name', - }, - { - name: 'region_iso_code', - type: 'alias', - path: 'source.geo.region_iso_code', - }, - ], - }, - ], - }, - ], - }, - ], - }, - { - key: 'activemq', - title: 'activemq', - release: 'ga', - description: 'Module for parsing ActiveMQ log files.\n', - fields: [ - { - name: 'activemq', - type: 'group', - description: '\n', - fields: [ - { - name: 'caller', - type: 'keyword', - description: 'Name of the caller issuing the logging request (class or resource).\n', - }, - { - name: 'thread', - type: 'keyword', - description: 'Thread that generated the logging event.\n', - }, - { - name: 'user', - type: 'keyword', - description: 'User that generated the logging event.\n', - }, - { - name: 'audit', - type: 'group', - description: 'Fields from ActiveMQ audit logs.\n', - fields: [], - }, - { - name: 'log', - type: 'group', - description: 'Fields from ActiveMQ application logs.\n', - fields: [ - { - name: 'stack_trace', - type: 'keyword', - }, - ], - }, - ], - }, - ], - }, - { - key: 'aws', - title: 'AWS', - release: 'beta', - description: 'Module for handling logs from AWS.\n', - fields: [ - { - name: 'aws', - type: 'group', - description: 'Fields from AWS logs.\n', - fields: [ - { - name: 'cloudtrail', - type: 'group', - release: 'beta', - default_field: false, - description: 'Fields for AWS CloudTrail logs.\n', - fields: [ - { - name: 'event_version', - type: 'keyword', - description: 'The CloudTrail version of the log event format.\n', - }, - { - name: 'user_identity', - type: 'group', - description: - 'The userIdentity element contains details about the type of IAM identity that made the request, and which credentials were used. If temporary credentials were used, the element shows how the credentials were obtained.', - fields: [ - { - name: 'type', - type: 'keyword', - description: 'The type of the identity\n', - }, - { - name: 'arn', - type: 'keyword', - description: - 'The Amazon Resource Name (ARN) of the principal that made the call.', - }, - { - name: 'access_key_id', - type: 'keyword', - description: 'The access key ID that was used to sign the request.', - }, - { - name: 'session_context', - type: 'group', - description: - 'If the request was made with temporary security credentials, an element that provides information about the session that was created for those credentials', - fields: [ - { - name: 'mfa_authenticated', - type: 'keyword', - description: - 'The value is true if the root user or IAM user whose credentials were used for the request also was authenticated with an MFA device; otherwise, false.', - }, - { - name: 'creation_date', - type: 'date', - description: - 'The date and time when the temporary security credentials were issued.', - }, - ], - }, - { - name: 'invoked_by', - type: 'keyword', - description: - 'The name of the AWS service that made the request, such as Amazon EC2 Auto Scaling or AWS Elastic Beanstalk.', - }, - { - name: 'session_issuer', - type: 'group', - description: - 'If the request was made with temporary security credentials, an element that provides information about how the credentials were obtained.', - fields: [ - { - name: 'type', - type: 'keyword', - description: - 'The source of the temporary security credentials, such as Root, IAMUser, or Role.', - }, - { - name: 'principal_id', - type: 'keyword', - description: - 'The internal ID of the entity that was used to get credentials.', - }, - { - name: 'arn', - type: 'keyword', - description: - 'The ARN of the source (account, IAM user, or role) that was used to get temporary security credentials.', - }, - { - name: 'account_id', - type: 'keyword', - description: - 'The account that owns the entity that was used to get credentials.', - }, - ], - }, - ], - }, - { - name: 'error_code', - type: 'keyword', - description: 'The AWS service error if the request returns an error.', - }, - { - name: 'error_message', - type: 'keyword', - description: 'If the request returns an error, the description of the error.', - }, - { - name: 'request_parameters', - type: 'keyword', - description: 'The parameters, if any, that were sent with the request.', - }, - { - name: 'response_elements', - type: 'keyword', - description: - 'The response element for actions that make changes (create, update, or delete actions).', - }, - { - name: 'additional_eventdata', - type: 'keyword', - description: - 'Additional data about the event that was not part of the request or response.', - }, - { - name: 'request_id', - type: 'keyword', - description: - 'The value that identifies the request. The service being called generates this value.', - }, - { - name: 'event_type', - type: 'keyword', - description: 'Identifies the type of event that generated the event record.', - }, - { - name: 'api_version', - type: 'keyword', - description: - 'Identifies the API version associated with the AwsApiCall eventType value.', - }, - { - name: 'management_event', - type: 'keyword', - description: - 'A Boolean value that identifies whether the event is a management event.', - }, - { - name: 'read_only', - type: 'keyword', - description: 'Identifies whether this operation is a read-only operation.', - }, - { - name: 'resources', - type: 'group', - description: 'A list of resources accessed in the event.', - fields: [ - { - name: 'arn', - type: 'keyword', - description: 'Resource ARNs', - }, - { - name: 'account_id', - type: 'keyword', - description: 'Account ID of the resource owner', - }, - { - name: 'type', - type: 'keyword', - description: - 'Resource type identifier in the format: AWS::aws-service-name::data-type-name', - }, - ], - }, - { - name: 'recipient_account_id', - type: 'keyword', - description: 'Represents the account ID that received this event.', - }, - { - name: 'service_event_details', - type: 'keyword', - description: - 'Identifies the service event, including what triggered the event and the result.', - }, - { - name: 'shared_event_id', - type: 'keyword', - description: - 'GUID generated by CloudTrail to uniquely identify CloudTrail events from the same AWS action that is sent to different AWS accounts.', - }, - { - name: 'vpc_endpoint_id', - type: 'keyword', - description: - 'Identifies the VPC endpoint in which requests were made from a VPC to another AWS service, such as Amazon S3.', - }, - { - name: 'console_login', - type: 'group', - description: 'Fields specific to ConsoleLogin events', - fields: [ - { - name: 'additional_eventdata', - type: 'group', - description: 'Additional Event Data for ConsoleLogin events\n', - fields: [ - { - name: 'mobile_version', - type: 'boolean', - description: 'Identifies whether ConsoleLogin was from mobile version', - }, - { - name: 'login_to', - type: 'keyword', - description: 'URL for ConsoleLogin', - }, - { - name: 'mfa_used', - type: 'boolean', - description: - 'Identifies whether multi factor authentication was used during ConsoleLogin', - }, - ], - }, - ], - }, - ], - }, - { - name: 'cloudwatch', - type: 'group', - release: 'beta', - default_field: false, - description: 'Fields for AWS CloudWatch logs.\n', - fields: [], - }, - { - name: 'ec2', - type: 'group', - release: 'beta', - default_field: false, - description: 'Fields for AWS EC2 logs in CloudWatch.\n', - fields: [ - { - name: 'ip_address', - type: 'keyword', - description: 'The internet address of the requester.\n', - }, - ], - }, - { - name: 'elb', - type: 'group', - release: 'ga', - description: 'Fields for AWS ELB logs.\n', - fields: [ - { - name: 'name', - type: 'keyword', - description: 'The name of the load balancer.\n', - }, - { - name: 'type', - type: 'keyword', - description: 'The type of the load balancer for v2 Load Balancers.\n', - }, - { - name: 'target_group.arn', - type: 'keyword', - description: 'The ARN of the target group handling the request.\n', - }, - { - name: 'listener', - type: 'keyword', - description: 'The ELB listener that received the connection.\n', - }, - { - name: 'protocol', - type: 'keyword', - description: 'The protocol of the load balancer (http or tcp).\n', - }, - { - name: 'request_processing_time.sec', - type: 'float', - description: - 'The total time in seconds since the connection or request is received until it is sent to a registered backend.\n', - }, - { - name: 'backend_processing_time.sec', - type: 'float', - description: - 'The total time in seconds since the connection is sent to the backend till the backend starts responding.\n', - }, - { - name: 'response_processing_time.sec', - type: 'float', - description: - 'The total time in seconds since the response is received from the backend till it is sent to the client.\n', - }, - { - name: 'connection_time.ms', - type: 'long', - description: - 'The total time of the connection in milliseconds, since it is opened till it is closed.\n', - }, - { - name: 'tls_handshake_time.ms', - type: 'long', - description: - 'The total time for the TLS handshake to complete in milliseconds once the connection has been established.\n', - }, - { - name: 'backend.ip', - type: 'keyword', - description: 'The IP address of the backend processing this connection.\n', - }, - { - name: 'backend.port', - type: 'keyword', - description: 'The port in the backend processing this connection.\n', - }, - { - name: 'backend.http.response.status_code', - type: 'keyword', - description: - 'The status code from the backend (status code sent to the client from ELB is stored in `http.response.status_code`\n', - }, - { - name: 'ssl_cipher', - type: 'keyword', - description: 'The SSL cipher used in TLS/SSL connections.\n', - }, - { - name: 'ssl_protocol', - type: 'keyword', - description: 'The SSL protocol used in TLS/SSL connections.\n', - }, - { - name: 'chosen_cert.arn', - type: 'keyword', - description: - 'The ARN of the chosen certificate presented to the client in TLS/SSL connections.\n', - }, - { - name: 'chosen_cert.serial', - type: 'keyword', - description: - 'The serial number of the chosen certificate presented to the client in TLS/SSL connections.\n', - }, - { - name: 'incoming_tls_alert', - type: 'keyword', - description: - 'The integer value of TLS alerts received by the load balancer from the client, if present.\n', - }, - { - name: 'tls_named_group', - type: 'keyword', - description: 'The TLS named group.\n', - }, - { - name: 'trace_id', - type: 'keyword', - description: 'The contents of the `X-Amzn-Trace-Id` header.\n', - }, - { - name: 'matched_rule_priority', - type: 'keyword', - description: - 'The priority value of the rule that matched the request, if a rule matched.\n', - }, - { - name: 'action_executed', - type: 'keyword', - description: - 'The action executed when processing the request (forward, fixed-response, authenticate...). It can contain several values.\n', - }, - { - name: 'redirect_url', - type: 'keyword', - description: 'The URL used if a redirection action was executed.\n', - }, - { - name: 'error.reason', - type: 'keyword', - description: 'The error reason if the executed action failed.', - }, - ], - }, - { - name: 's3access', - type: 'group', - release: 'ga', - description: 'Fields for AWS S3 server access logs.\n', - fields: [ - { - name: 'bucket_owner', - type: 'keyword', - description: 'The canonical user ID of the owner of the source bucket.\n', - }, - { - name: 'bucket', - type: 'keyword', - description: 'The name of the bucket that the request was processed against.\n', - }, - { - name: 'remote_ip', - type: 'ip', - description: 'The apparent internet address of the requester.\n', - }, - { - name: 'requester', - type: 'keyword', - description: - 'The canonical user ID of the requester, or a - for unauthenticated requests.\n', - }, - { - name: 'request_id', - type: 'keyword', - description: 'A string generated by Amazon S3 to uniquely identify each request.\n', - }, - { - name: 'operation', - type: 'keyword', - description: - 'The operation listed here is declared as SOAP.operation, REST.HTTP_method.resource_type, WEBSITE.HTTP_method.resource_type, or BATCH.DELETE.OBJECT.\n', - }, - { - name: 'key', - type: 'keyword', - description: - 'The "key" part of the request, URL encoded, or "-" if the operation does not take a key parameter.\n', - }, - { - name: 'request_uri', - type: 'keyword', - description: 'The Request-URI part of the HTTP request message.\n', - }, - { - name: 'http_status', - type: 'long', - description: 'The numeric HTTP status code of the response.\n', - }, - { - name: 'error_code', - type: 'keyword', - description: 'The Amazon S3 Error Code, or "-" if no error occurred.\n', - }, - { - name: 'bytes_sent', - type: 'long', - description: - 'The number of response bytes sent, excluding HTTP protocol overhead, or "-" if zero.\n', - }, - { - name: 'object_size', - type: 'long', - description: 'The total size of the object in question.\n', - }, - { - name: 'total_time', - type: 'long', - description: - "The number of milliseconds the request was in flight from the server's perspective.\n", - }, - { - name: 'turn_around_time', - type: 'long', - description: - 'The number of milliseconds that Amazon S3 spent processing your request.\n', - }, - { - name: 'referrer', - type: 'keyword', - description: 'The value of the HTTP Referrer header, if present.\n', - }, - { - name: 'user_agent', - type: 'keyword', - description: 'The value of the HTTP User-Agent header.\n', - }, - { - name: 'version_id', - type: 'keyword', - description: - 'The version ID in the request, or "-" if the operation does not take a versionId parameter.\n', - }, - { - name: 'host_id', - type: 'keyword', - description: 'The x-amz-id-2 or Amazon S3 extended request ID.\n', - }, - { - name: 'signature_version', - type: 'keyword', - description: - 'The signature version, SigV2 or SigV4, that was used to authenticate the request or a - for unauthenticated requests.\n', - }, - { - name: 'cipher_suite', - type: 'keyword', - description: - 'The Secure Sockets Layer (SSL) cipher that was negotiated for HTTPS request or a - for HTTP.\n', - }, - { - name: 'authentication_type', - type: 'keyword', - description: - 'The type of request authentication used, AuthHeader for authentication headers, QueryString for query string (pre-signed URL) or a - for unauthenticated requests.\n', - }, - { - name: 'host_header', - type: 'keyword', - description: 'The endpoint used to connect to Amazon S3.\n', - }, - { - name: 'tls_version', - type: 'keyword', - description: - 'The Transport Layer Security (TLS) version negotiated by the client.\n', - }, - ], - }, - { - name: 'vpcflow', - type: 'group', - release: 'beta', - description: 'Fields for AWS VPC flow logs.\n', - fields: [ - { - name: 'version', - type: 'keyword', - description: - 'The VPC Flow Logs version. If you use the default format, the version is 2. If you specify a custom format, the version is 3.\n', - }, - { - name: 'account_id', - type: 'keyword', - description: 'The AWS account ID for the flow log.\n', - }, - { - name: 'interface_id', - type: 'keyword', - description: 'The ID of the network interface for which the traffic is recorded.\n', - }, - { - name: 'action', - type: 'keyword', - description: 'The action that is associated with the traffic, ACCEPT or REJECT.\n', - }, - { - name: 'log_status', - type: 'keyword', - description: 'The logging status of the flow log, OK, NODATA or SKIPDATA.\n', - }, - { - name: 'instance_id', - type: 'keyword', - description: - "The ID of the instance that's associated with network interface for which the traffic is recorded, if the instance is owned by you.\n", - }, - { - name: 'pkt_srcaddr', - type: 'ip', - description: 'The packet-level (original) source IP address of the traffic.\n', - }, - { - name: 'pkt_dstaddr', - type: 'ip', - description: - 'The packet-level (original) destination IP address for the traffic.\n', - }, - { - name: 'vpc_id', - type: 'keyword', - description: - 'The ID of the VPC that contains the network interface for which the traffic is recorded.\n', - }, - { - name: 'subnet_id', - type: 'keyword', - description: - 'The ID of the subnet that contains the network interface for which the traffic is recorded.\n', - }, - { - name: 'tcp_flags', - type: 'keyword', - description: - 'The bitmask value for the following TCP flags: 2=SYN,18=SYN-ACK,1=FIN,4=RST\n', - }, - { - name: 'type', - type: 'keyword', - description: 'The type of traffic: IPv4, IPv6, or EFA.\n', - }, - ], - }, - ], - }, - ], - }, - { - key: 'azure', - title: 'Azure', - release: 'beta', - description: 'Azure Module\n', - fields: [ - { - name: 'azure', - type: 'group', - description: '\n', - fields: [ - { - name: 'subscription_id', - type: 'keyword', - description: 'Azure subscription ID\n', - }, - { - name: 'correlation_id', - type: 'keyword', - description: 'Correlation ID\n', - }, - { - name: 'tenant_id', - type: 'keyword', - description: 'tenant ID\n', - }, - { - name: 'resource', - type: 'group', - description: 'Resource\n', - fields: [ - { - name: 'id', - type: 'keyword', - description: 'Resource ID\n', - }, - { - name: 'group', - type: 'keyword', - description: 'Resource group\n', - }, - { - name: 'provider', - type: 'keyword', - description: 'Resource type/namespace\n', - }, - { - name: 'namespace', - type: 'keyword', - description: 'Resource type/namespace\n', - }, - { - name: 'name', - type: 'keyword', - description: 'Name\n', - }, - { - name: 'authorization_rule', - type: 'keyword', - description: 'Authorization rule\n', - }, - ], - }, - { - name: 'activitylogs', - type: 'group', - release: 'beta', - description: 'Fields for Azure activity logs.\n', - fields: [ - { - name: 'identity', - type: 'group', - description: 'Identity\n', - fields: [ - { - name: 'claims_initiated_by_user', - type: 'group', - description: 'Claims initiated by user\n', - fields: [ - { - name: 'name', - type: 'keyword', - description: 'Name\n', - }, - { - name: 'givenname', - type: 'keyword', - description: 'Givenname\n', - }, - { - name: 'surname', - type: 'keyword', - description: 'Surname\n', - }, - { - name: 'fullname', - type: 'keyword', - description: 'Fullname\n', - }, - { - name: 'schema', - type: 'keyword', - description: 'Schema\n', - }, - ], - }, - { - name: 'claims.*', - type: 'object', - object_type: 'keyword', - object_type_mapping_type: '*', - description: 'Claims\n', - }, - { - name: 'authorization', - type: 'group', - description: 'Authorization\n', - fields: [ - { - name: 'scope', - type: 'keyword', - description: 'Scope\n', - }, - { - name: 'action', - type: 'keyword', - description: 'Action\n', - }, - { - name: 'evidence', - type: 'group', - description: 'Evidence\n', - fields: [ - { - name: 'role_assignment_scope', - type: 'keyword', - description: 'Role assignment scope\n', - }, - { - name: 'role_definition_id', - type: 'keyword', - description: 'Role definition ID\n', - }, - { - name: 'role', - type: 'keyword', - description: 'Role\n', - }, - { - name: 'role_assignment_id', - type: 'keyword', - description: 'Role assignment ID\n', - }, - { - name: 'principal_id', - type: 'keyword', - description: 'Principal ID\n', - }, - { - name: 'principal_type', - type: 'keyword', - description: 'Principal type\n', - }, - ], - }, - ], - }, - ], - }, - { - name: 'operation_name', - type: 'keyword', - description: 'Operation name\n', - }, - { - name: 'result_signature', - type: 'keyword', - description: 'Result signature\n', - }, - { - name: 'category', - type: 'keyword', - description: 'Category\n', - }, - { - name: 'properties', - type: 'group', - description: 'Properties\n', - fields: [ - { - name: 'service_request_id', - type: 'keyword', - description: 'Service Request Id\n', - }, - { - name: 'status_code', - type: 'keyword', - description: 'Status code\n', - }, - ], - }, - ], - }, - { - name: 'auditlogs', - type: 'group', - description: 'Fields for Azure audit logs.\n', - fields: [ - { - name: 'operation_name', - type: 'keyword', - description: 'The operation name\n', - }, - { - name: 'operation_version', - type: 'keyword', - description: 'The operation version\n', - }, - { - name: 'identity', - type: 'keyword', - description: 'Identity\n', - }, - { - name: 'tenant_id', - type: 'keyword', - description: 'Tenant ID\n', - }, - { - name: 'result_signature', - type: 'keyword', - description: 'Result signature\n', - }, - { - name: 'properties', - type: 'group', - description: 'The audit log properties\n', - fields: [ - { - name: 'result', - type: 'keyword', - description: 'Log result\n', - }, - { - name: 'activity_display_name', - type: 'keyword', - description: 'Activity display name\n', - }, - { - name: 'result_reason', - type: 'keyword', - description: 'Reason for the log result\n', - }, - { - name: 'correlation_id', - type: 'keyword', - description: 'Correlation ID\n', - }, - { - name: 'logged_by_service', - type: 'keyword', - description: 'Logged by service\n', - }, - { - name: 'operation_type', - type: 'keyword', - description: 'Operation type\n', - }, - { - name: 'id', - type: 'keyword', - description: 'ID\n', - }, - { - name: 'activity_datetime', - type: 'date', - description: 'Activity timestamp\n', - }, - { - name: 'category', - type: 'keyword', - description: 'category\n', - }, - { - name: 'target_resources.*', - type: 'group', - object_type_mapping_type: '*', - description: 'Target resources\n', - fields: [ - { - name: 'display_name', - type: 'keyword', - description: 'Display name\n', - }, - { - name: 'id', - type: 'keyword', - description: 'ID\n', - }, - { - name: 'type', - type: 'keyword', - description: 'Type\n', - }, - { - name: 'ip_address', - type: 'keyword', - description: 'ip Address\n', - }, - { - name: 'user_principal_name', - type: 'keyword', - description: 'User principal name\n', - }, - { - name: 'modified_properties.*', - type: 'group', - object_type: 'keyword', - object_type_mapping_type: '*', - description: 'Modified properties\n', - fields: [ - { - name: 'new_value', - type: 'keyword', - description: 'New value\n', - }, - { - name: 'display_name', - type: 'keyword', - description: 'Display value\n', - }, - { - name: 'old_value', - type: 'keyword', - description: 'Old value\n', - }, - ], - }, - ], - }, - { - name: 'initiated_by', - type: 'group', - description: 'Information regarding the initiator\n', - fields: [ - { - name: 'app', - type: 'group', - description: 'App\n', - fields: [ - { - name: 'servicePrincipalName', - type: 'keyword', - description: 'Service principal name\n', - }, - { - name: 'displayName', - type: 'keyword', - description: 'Display name\n', - }, - { - name: 'appId', - type: 'keyword', - description: 'App ID\n', - }, - { - name: 'servicePrincipalId', - type: 'keyword', - description: 'Service principal ID\n', - }, - ], - }, - { - name: 'user', - type: 'group', - description: 'User\n', - fields: [ - { - name: 'userPrincipalName', - type: 'keyword', - description: 'User principal name\n', - }, - { - name: 'displayName', - type: 'keyword', - description: 'Display name\n', - }, - { - name: 'id', - type: 'keyword', - description: 'ID\n', - }, - { - name: 'ipAddress', - type: 'keyword', - description: 'ip Address\n', - }, - ], - }, - ], - }, - ], - }, - ], - }, - { - name: 'signinlogs', - type: 'group', - description: 'Fields for Azure sign-in logs.\n', - fields: [ - { - name: 'operation_name', - type: 'keyword', - description: 'The operation name\n', - }, - { - name: 'operation_version', - type: 'keyword', - description: 'The operation version\n', - }, - { - name: 'tenant_id', - type: 'keyword', - description: 'Tenant ID\n', - }, - { - name: 'result_signature', - type: 'keyword', - description: 'Result signature\n', - }, - { - name: 'result_description', - type: 'keyword', - description: 'Result description\n', - }, - { - name: 'identity', - type: 'keyword', - description: 'Identity\n', - }, - { - name: 'properties', - type: 'group', - description: 'The signin log properties\n', - fields: [ - { - name: 'id', - type: 'keyword', - description: 'ID\n', - }, - { - name: 'created_at', - type: 'date', - description: 'Created date time\n', - }, - { - name: 'user_display_name', - type: 'keyword', - description: 'User display name\n', - }, - { - name: 'correlation_id', - type: 'keyword', - description: 'Correlation ID\n', - }, - { - name: 'user_principal_name', - type: 'keyword', - description: 'User principal name\n', - }, - { - name: 'user_id', - type: 'keyword', - description: 'User ID\n', - }, - { - name: 'app_id', - type: 'keyword', - description: 'App ID\n', - }, - { - name: 'app_display_name', - type: 'keyword', - description: 'App display name\n', - }, - { - name: 'ip_address', - type: 'keyword', - description: 'Ip address\n', - }, - { - name: 'client_app_used', - type: 'keyword', - description: 'Client app used\n', - }, - { - name: 'conditional_access_status', - type: 'keyword', - description: 'Conditional access status\n', - }, - { - name: 'original_request_id', - type: 'keyword', - description: 'Original request ID\n', - }, - { - name: 'is_interactive', - type: 'keyword', - description: 'Is interactive\n', - }, - { - name: 'token_issuer_name', - type: 'keyword', - description: 'Token issuer name\n', - }, - { - name: 'token_issuer_type', - type: 'keyword', - description: 'Token issuer type\n', - }, - { - name: 'processing_time_ms', - type: 'float', - description: 'Processing time in milliseconds\n', - }, - { - name: 'risk_detail', - type: 'keyword', - description: 'Risk detail\n', - }, - { - name: 'risk_level_aggregated', - type: 'keyword', - description: 'Risk level aggregated\n', - }, - { - name: 'risk_level_during_signin', - type: 'keyword', - description: 'Risk level during signIn\n', - }, - { - name: 'risk_state', - type: 'keyword', - description: 'Risk state\n', - }, - { - name: 'resource_display_name', - type: 'keyword', - description: 'Resource display name\n', - }, - { - name: 'status', - type: 'group', - description: 'Status\n', - fields: [ - { - name: 'error_code', - type: 'keyword', - description: 'Error code\n', - }, - ], - }, - { - name: 'device_detail', - type: 'group', - description: 'Status\n', - fields: [ - { - name: 'device_id', - type: 'keyword', - description: 'Device ID\n', - }, - { - name: 'operating_system', - type: 'keyword', - description: 'Operating system\n', - }, - { - name: 'browser', - type: 'keyword', - description: 'Browser\n', - }, - { - name: 'display_name', - type: 'keyword', - description: 'Display name\n', - }, - { - name: 'trust_type', - type: 'keyword', - description: 'Trust type\n', - }, - ], - }, - { - name: 'service_principal_id', - type: 'keyword', - description: 'Status\n', - }, - ], - }, - ], - }, - ], - }, - ], - }, - { - key: 'cef-module', - title: 'CEF', - description: - 'Module for receiving CEF logs over Syslog. The module adds vendor specific fields in addition to the fields the decode_cef processor provides.\n', - fields: [ - { - name: 'forcepoint', - type: 'group', - default_field: false, - description: 'Fields for Forcepoint Custom String mappings\n', - fields: [ - { - name: 'virus_id', - type: 'keyword', - description: 'Virus ID\n', - }, - ], - }, - { - name: 'checkpoint', - type: 'group', - default_field: false, - description: 'Fields for Check Point custom string mappings.\n', - fields: [ - { - name: 'app_risk', - type: 'keyword', - description: 'Application risk.', - }, - { - name: 'app_severity', - type: 'keyword', - description: 'Application threat severity.', - }, - { - name: 'app_sig_id', - type: 'keyword', - description: 'The signature ID which the application was detected by.', - }, - { - name: 'auth_method', - type: 'keyword', - description: 'Password authentication protocol used.', - }, - { - name: 'category', - type: 'keyword', - description: 'Category.', - }, - { - name: 'confidence_level', - type: 'keyword', - description: 'Confidence level determined.', - }, - { - name: 'connectivity_state', - type: 'keyword', - description: 'Connectivity state.', - }, - { - name: 'cookie', - type: 'keyword', - description: 'IKE cookie.', - }, - { - name: 'dst_phone_number', - type: 'keyword', - description: 'Destination IP-Phone.', - }, - { - name: 'email_control', - type: 'keyword', - description: 'Engine name.', - }, - { - name: 'email_id', - type: 'keyword', - description: 'Internal email ID.', - }, - { - name: 'email_recipients_num', - type: 'long', - description: 'Number of recipients.', - }, - { - name: 'email_session_id', - type: 'keyword', - description: 'Internal email session ID.', - }, - { - name: 'email_spool_id', - type: 'keyword', - description: 'Internal email spool ID.', - }, - { - name: 'email_subject', - type: 'keyword', - description: 'Email subject.', - }, - { - name: 'event_count', - type: 'long', - description: 'Number of events associated with the log.', - }, - { - name: 'file_hash', - type: 'keyword', - description: 'File hash (SHA1 or MD5).', - }, - { - name: 'frequency', - type: 'keyword', - description: 'Scan frequency.', - }, - { - name: 'icmp_type', - type: 'long', - description: 'ICMP type.', - }, - { - name: 'icmp_code', - type: 'long', - description: 'ICMP code.', - }, - { - name: 'identity_type', - type: 'keyword', - description: 'Identity type.', - }, - { - name: 'incident_extension', - type: 'keyword', - description: 'Format of original data.', - }, - { - name: 'integrity_av_invoke_type', - type: 'keyword', - description: 'Scan invoke type.', - }, - { - name: 'peer_gateway', - type: 'ip', - description: 'Main IP of the peer Security Gateway.', - }, - { - name: 'performance_impact', - type: 'keyword', - description: 'Protection performance impact.', - }, - { - name: 'protection_id', - type: 'keyword', - description: 'Protection malware ID.', - }, - { - name: 'protection_name', - type: 'keyword', - description: 'Specific signature name of the attack.', - }, - { - name: 'protection_type', - type: 'keyword', - description: 'Type of protection used to detect the attack.', - }, - { - name: 'scan_result', - type: 'keyword', - description: 'Scan result.', - }, - { - name: 'sensor_mode', - type: 'keyword', - description: 'Sensor mode.', - }, - { - name: 'severity', - type: 'keyword', - description: 'Threat severity.', - }, - { - name: 'malware_status', - type: 'keyword', - description: 'Malware status.', - }, - { - name: 'subscription_expiration', - type: 'date', - description: 'The expiration date of the subscription.', - }, - { - name: 'tcp_flags', - type: 'keyword', - description: 'TCP packet flags.', - }, - { - name: 'termination_reason', - type: 'keyword', - description: 'Termination reason.', - }, - { - name: 'update_status', - type: 'keyword', - description: 'Update status.', - }, - { - name: 'user_status', - type: 'keyword', - description: 'User response.', - }, - { - name: 'uuid', - type: 'keyword', - description: 'External ID.', - }, - { - name: 'virus_name', - type: 'keyword', - description: 'Virus name.', - }, - { - name: 'malware_name', - type: 'keyword', - description: 'Malware name.', - }, - { - name: 'malware_family', - type: 'keyword', - description: 'Malware family.', - }, - { - name: 'voip_log_type', - type: 'keyword', - description: 'VoIP log types.', - }, - ], - }, - { - name: 'cef.extensions', - type: 'group', - default_field: false, - description: 'Extra vendor-specific extensions.\n', - fields: [ - { - name: 'cp_app_risk', - type: 'keyword', - }, - { - name: 'cp_severity', - type: 'keyword', - }, - { - name: 'ifname', - type: 'keyword', - }, - { - name: 'inzone', - type: 'keyword', - }, - { - name: 'layer_uuid', - type: 'keyword', - }, - { - name: 'layer_name', - type: 'keyword', - }, - { - name: 'logid', - type: 'keyword', - }, - { - name: 'loguid', - type: 'keyword', - }, - { - name: 'match_id', - type: 'keyword', - }, - { - name: 'nat_addtnl_rulenum', - type: 'keyword', - }, - { - name: 'nat_rulenum', - type: 'keyword', - }, - { - name: 'origin', - type: 'keyword', - }, - { - name: 'originsicname', - type: 'keyword', - }, - { - name: 'outzone', - type: 'keyword', - }, - { - name: 'parent_rule', - type: 'keyword', - }, - { - name: 'product', - type: 'keyword', - }, - { - name: 'rule_action', - type: 'keyword', - }, - { - name: 'rule_uid', - type: 'keyword', - }, - { - name: 'sequencenum', - type: 'keyword', - }, - { - name: 'service_id', - type: 'keyword', - }, - { - name: 'version', - type: 'keyword', - }, - ], - }, - ], - }, - { - key: 'cisco', - title: 'Cisco', - description: 'Module for handling Cisco network device logs.\n', - fields: [ - { - name: 'cisco', - type: 'group', - description: 'Fields from Cisco logs.\n', - fields: [ - { - name: 'asa', - type: 'group', - description: 'Fields for Cisco ASA Firewall.\n', - fields: [ - { - name: 'message_id', - type: 'keyword', - description: 'The Cisco ASA message identifier.\n', - }, - { - name: 'suffix', - type: 'keyword', - example: 'session', - description: 'Optional suffix after %ASA identifier.\n', - }, - { - name: 'source_interface', - type: 'keyword', - description: 'Source interface for the flow or event.\n', - }, - { - name: 'destination_interface', - type: 'keyword', - description: 'Destination interface for the flow or event.\n', - }, - { - name: 'rule_name', - type: 'keyword', - description: 'Name of the Access Control List rule that matched this event.\n', - }, - { - name: 'source_username', - type: 'keyword', - description: 'Name of the user that is the source for this event.\n', - }, - { - name: 'destination_username', - type: 'keyword', - description: 'Name of the user that is the destination for this event.\n', - }, - { - name: 'mapped_source_ip', - type: 'ip', - description: 'The translated source IP address.\n', - }, - { - name: 'mapped_source_port', - type: 'long', - description: 'The translated source port.\n', - }, - { - name: 'mapped_destination_ip', - type: 'ip', - description: 'The translated destination IP address.\n', - }, - { - name: 'mapped_destination_port', - type: 'long', - description: 'The translated destination port.\n', - }, - { - name: 'threat_level', - type: 'keyword', - description: - 'Threat level for malware / botnet traffic. One of very-low, low, moderate, high or very-high.\n', - }, - { - name: 'threat_category', - type: 'keyword', - description: - 'Category for the malware / botnet traffic. For example: virus, botnet, trojan, etc.\n', - }, - { - name: 'connection_id', - type: 'keyword', - description: 'Unique identifier for a flow.\n', - }, - { - name: 'icmp_type', - type: 'short', - description: 'ICMP type.\n', - }, - { - name: 'icmp_code', - type: 'short', - description: 'ICMP code.\n', - }, - { - name: 'connection_type', - type: 'keyword', - default_field: false, - description: 'The VPN connection type\n', - }, - { - name: 'dap_records', - default_field: false, - type: 'keyword', - description: 'The assigned DAP records\n', - }, - ], - }, - { - name: 'ftd', - type: 'group', - description: 'Fields for Cisco Firepower Threat Defense Firewall.\n', - fields: [ - { - name: 'message_id', - type: 'keyword', - description: 'The Cisco FTD message identifier.\n', - }, - { - name: 'suffix', - type: 'keyword', - example: 'session', - description: 'Optional suffix after %FTD identifier.\n', - }, - { - name: 'source_interface', - type: 'keyword', - description: 'Source interface for the flow or event.\n', - }, - { - name: 'destination_interface', - type: 'keyword', - description: 'Destination interface for the flow or event.\n', - }, - { - name: 'rule_name', - type: 'keyword', - description: 'Name of the Access Control List rule that matched this event.\n', - }, - { - name: 'source_username', - type: 'keyword', - description: 'Name of the user that is the source for this event.\n', - }, - { - name: 'destination_username', - type: 'keyword', - description: 'Name of the user that is the destination for this event.\n', - }, - { - name: 'mapped_source_ip', - type: 'ip', - description: 'The translated source IP address. Use ECS source.nat.ip.\n', - }, - { - name: 'mapped_source_port', - type: 'long', - description: 'The translated source port. Use ECS source.nat.port.\n', - }, - { - name: 'mapped_destination_ip', - type: 'ip', - description: 'The translated destination IP address. Use ECS destination.nat.ip.\n', - }, - { - name: 'mapped_destination_port', - type: 'long', - description: 'The translated destination port. Use ECS destination.nat.port.\n', - }, - { - name: 'threat_level', - type: 'keyword', - description: - 'Threat level for malware / botnet traffic. One of very-low, low, moderate, high or very-high.\n', - }, - { - name: 'threat_category', - type: 'keyword', - description: - 'Category for the malware / botnet traffic. For example: virus, botnet, trojan, etc.\n', - }, - { - name: 'connection_id', - type: 'keyword', - description: 'Unique identifier for a flow.\n', - }, - { - name: 'icmp_type', - type: 'short', - description: 'ICMP type.\n', - }, - { - name: 'icmp_code', - type: 'short', - description: 'ICMP code.\n', - }, - { - name: 'security', - type: 'object', - description: 'Raw fields for Security Events.', - }, - { - name: 'connection_type', - type: 'keyword', - default_field: false, - description: 'The VPN connection type\n', - }, - { - name: 'dap_records', - type: 'keyword', - default_field: false, - description: 'The assigned DAP records\n', - }, - ], - }, - { - name: 'ios', - type: 'group', - description: 'Fields for Cisco IOS logs.\n', - fields: [ - { - name: 'access_list', - type: 'keyword', - description: 'Name of the IP access list.\n', - }, - { - name: 'facility', - type: 'keyword', - example: 'SEC', - description: - 'The facility to which the message refers (for example, SNMP, SYS, and so forth). A facility can be a hardware device, a protocol, or a module of the system software. It denotes the source or the cause of the system message.\n', - }, - ], - }, - ], - }, - ], - }, - { - key: 'coredns', - title: 'Coredns', - description: 'Module for handling logs produced by coredns\n', - fields: [ - { - name: 'coredns', - type: 'group', - description: 'coredns fields after normalization\n', - fields: [ - { - name: 'id', - type: 'keyword', - description: 'id of the DNS transaction\n', - }, - { - name: 'query.size', - type: 'integer', - format: 'bytes', - description: 'size of the DNS query\n', - }, - { - name: 'query.class', - type: 'keyword', - description: 'DNS query class\n', - }, - { - name: 'query.name', - type: 'keyword', - description: 'DNS query name\n', - }, - { - name: 'query.type', - type: 'keyword', - description: 'DNS query type\n', - }, - { - name: 'response.code', - type: 'keyword', - description: 'DNS response code\n', - }, - { - name: 'response.flags', - type: 'keyword', - description: 'DNS response flags\n', - }, - { - name: 'response.size', - type: 'integer', - format: 'bytes', - description: 'size of the DNS response\n', - }, - { - name: 'dnssec_ok', - type: 'boolean', - description: 'dnssec flag\n', - }, - ], - }, - ], - }, - { - key: 'envoyproxy', - title: 'Envoyproxy', - description: 'Module for handling logs produced by envoy\n', - fields: [ - { - name: 'envoyproxy', - type: 'group', - description: 'Fields from envoy proxy logs after normalization\n', - fields: [ - { - name: 'log_type', - type: 'keyword', - description: 'Envoy log type, normally ACCESS\n', - }, - { - name: 'response_flags', - type: 'keyword', - description: 'Response flags\n', - }, - { - name: 'upstream_service_time', - type: 'long', - format: 'duration', - input_format: 'nanoseconds', - description: 'Upstream service time in nanoseconds\n', - }, - { - name: 'request_id', - type: 'keyword', - description: 'ID of the request\n', - }, - { - name: 'authority', - type: 'keyword', - description: 'Envoy proxy authority field\n', - }, - { - name: 'proxy_type', - type: 'keyword', - description: 'Envoy proxy type, tcp or http\n', - }, - ], - }, - ], - }, - { - key: 'googlecloud', - title: 'Google Cloud', - description: 'Module for handling logs from Google Cloud.\n', - fields: [ - { - name: 'googlecloud', - type: 'group', - description: 'Fields from Google Cloud logs.\n', - fields: [ - { - name: 'destination.instance', - type: 'group', - description: - 'If the destination of the connection was a VM located on the same VPC, this field is populated with VM instance details. In a Shared VPC configuration, project_id corresponds to the project that owns the instance, usually the service project.\n', - fields: [ - { - name: 'project_id', - type: 'keyword', - description: 'ID of the project containing the VM.\n', - }, - { - name: 'region', - type: 'keyword', - description: 'Region of the VM.\n', - }, - { - name: 'zone', - type: 'keyword', - description: 'Zone of the VM.\n', - }, - ], - }, - { - name: 'destination.vpc', - type: 'group', - description: - 'If the destination of the connection was a VM located on the same VPC, this field is populated with VPC network details. In a Shared VPC configuration, project_id corresponds to that of the host project.\n', - fields: [ - { - name: 'project_id', - type: 'keyword', - description: 'ID of the project containing the VM.\n', - }, - { - name: 'vpc_name', - type: 'keyword', - description: 'VPC on which the VM is operating.\n', - }, - { - name: 'subnetwork_name', - type: 'keyword', - description: 'Subnetwork on which the VM is operating.\n', - }, - ], - }, - { - name: 'source.instance', - type: 'group', - description: - 'If the source of the connection was a VM located on the same VPC, this field is populated with VM instance details. In a Shared VPC configuration, project_id corresponds to the project that owns the instance, usually the service project.\n', - fields: [ - { - name: 'project_id', - type: 'keyword', - description: 'ID of the project containing the VM.\n', - }, - { - name: 'region', - type: 'keyword', - description: 'Region of the VM.\n', - }, - { - name: 'zone', - type: 'keyword', - description: 'Zone of the VM.\n', - }, - ], - }, - { - name: 'source.vpc', - type: 'group', - description: - 'If the source of the connection was a VM located on the same VPC, this field is populated with VPC network details. In a Shared VPC configuration, project_id corresponds to that of the host project.\n', - fields: [ - { - name: 'project_id', - type: 'keyword', - description: 'ID of the project containing the VM.\n', - }, - { - name: 'vpc_name', - type: 'keyword', - description: 'VPC on which the VM is operating.\n', - }, - { - name: 'subnetwork_name', - type: 'keyword', - description: 'Subnetwork on which the VM is operating.\n', - }, - ], - }, - { - name: 'audit', - type: 'group', - description: 'Fields for Google Cloud audit logs.\n', - fields: [ - { - name: 'type', - type: 'keyword', - description: 'Type property.\n', - }, - { - name: 'authentication_info', - type: 'group', - description: 'Authentication information.\n', - fields: [ - { - name: 'principal_email', - type: 'keyword', - description: - 'The email address of the authenticated user making the request.\n', - }, - { - name: 'authority_selector', - type: 'keyword', - description: - 'The authority selector specified by the requestor, if any. It is not guaranteed that the principal was allowed to use this authority.\n', - }, - ], - }, - { - name: 'authorization_info', - type: 'array', - description: 'Authorization information for the operation.\n', - fields: [ - { - name: 'permission', - type: 'keyword', - description: 'The required IAM permission.\n', - }, - { - name: 'granted', - type: 'boolean', - description: - 'Whether or not authorization for resource and permission was granted.\n', - }, - { - name: 'resource_attributes', - type: 'group', - description: 'The attributes of the resource.\n', - fields: [ - { - name: 'service', - type: 'keyword', - description: 'The name of the service.\n', - }, - { - name: 'name', - type: 'keyword', - description: 'The name of the resource.\n', - }, - { - name: 'type', - type: 'keyword', - description: 'The type of the resource.\n', - }, - ], - }, - ], - }, - { - name: 'method_name', - type: 'keyword', - description: - "The name of the service method or operation. For API calls, this should be the name of the API method. For example, 'google.datastore.v1.Datastore.RunQuery'.\n", - }, - { - name: 'num_response_items', - type: 'long', - description: - 'The number of items returned from a List or Query API method, if applicable.\n', - }, - { - name: 'request', - type: 'group', - description: 'The operation request.\n', - fields: [ - { - name: 'proto_name', - type: 'keyword', - description: 'Type property of the request.\n', - }, - { - name: 'filter', - type: 'keyword', - description: 'Filter of the request.\n', - }, - { - name: 'name', - type: 'keyword', - description: 'Name of the request.\n', - }, - { - name: 'resource_name', - type: 'keyword', - description: 'Name of the request resource.\n', - }, - ], - }, - { - name: 'request_metadata', - type: 'group', - description: 'Metadata about the request.\n', - fields: [ - { - name: 'caller_ip', - type: 'ip', - description: 'The IP address of the caller.\n', - }, - { - name: 'caller_supplied_user_agent', - type: 'keyword', - description: - 'The user agent of the caller. This information is not authenticated and should be treated accordingly.\n', - }, - ], - }, - { - name: 'resource_name', - type: 'keyword', - description: - "The resource or collection that is the target of the operation. The name is a scheme-less URI, not including the API service name. For example, 'shelves/SHELF_ID/books'.\n", - }, - { - name: 'resource_location', - type: 'group', - description: 'The location of the resource.\n', - fields: [ - { - name: 'current_locations', - type: 'keyword', - description: 'Current locations of the resource.\n', - }, - ], - }, - { - name: 'service_name', - type: 'keyword', - description: - 'The name of the API service performing the operation. For example, datastore.googleapis.com.\n', - }, - { - name: 'status', - type: 'group', - description: 'The status of the overall operation.\n', - fields: [ - { - name: 'code', - type: 'integer', - description: - 'The status code, which should be an enum value of google.rpc.Code.\n', - }, - { - name: 'message', - type: 'keyword', - description: - 'A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the google.rpc.Status.details field, or localized by the client.\n', - }, - ], - }, - ], - }, - { - name: 'firewall', - type: 'group', - description: 'Fields for Google Cloud Firewall logs.\n', - fields: [ - { - name: 'rule_details', - type: 'group', - description: 'Description of the firewall rule that matched this connection.\n', - fields: [ - { - name: 'priority', - type: 'long', - description: 'The priority for the firewall rule.', - }, - { - name: 'action', - type: 'keyword', - description: 'Action that the rule performs on match.', - }, - { - name: 'direction', - type: 'keyword', - description: 'Direction of traffic that matches this rule.', - }, - { - name: 'reference', - type: 'keyword', - description: 'Reference to the firewall rule.', - }, - { - name: 'source_range', - type: 'keyword', - description: 'List of source ranges that the firewall rule applies to.', - }, - { - name: 'destination_range', - type: 'keyword', - description: 'List of destination ranges that the firewall applies to.', - }, - { - name: 'source_tag', - type: 'keyword', - description: 'List of all the source tags that the firewall rule applies to.\n', - }, - { - name: 'target_tag', - type: 'keyword', - description: 'List of all the target tags that the firewall rule applies to.\n', - }, - { - name: 'ip_port_info', - type: 'array', - description: 'List of ip protocols and applicable port ranges for rules.\n', - }, - { - name: 'source_service_account', - type: 'keyword', - description: - 'List of all the source service accounts that the firewall rule applies to.\n', - }, - { - name: 'target_service_account', - type: 'keyword', - description: - 'List of all the target service accounts that the firewall rule applies to.\n', - }, - ], - }, - ], - }, - { - name: 'vpcflow', - type: 'group', - description: 'Fields for Google Cloud VPC flow logs.\n', - fields: [ - { - name: 'reporter', - type: 'keyword', - description: "The side which reported the flow. Can be either 'SRC' or 'DEST'.\n", - }, - { - name: 'rtt.ms', - type: 'long', - description: - 'Latency as measured (for TCP flows only) during the time interval. This is the time elapsed between sending a SEQ and receiving a corresponding ACK and it contains the network RTT as well as the application related delay.\n', - }, - ], - }, - ], - }, - ], - }, - { - key: 'ibmmq', - title: 'ibmmq', - description: 'ibmmq Module\n', - release: 'ga', - fields: [ - { - name: 'ibmmq', - type: 'group', - description: '\n', - fields: [ - { - name: 'errorlog', - description: 'IBM MQ error logs', - type: 'group', - fields: [ - { - name: 'installation', - description: - 'This is the installation name which can be given at installation time.\nEach installation of IBM MQ on UNIX, Linux, and Windows, has a unique identifier known as an installation name. The installation name is used to associate things such as queue managers and configuration files with an installation.\n', - type: 'keyword', - }, - { - name: 'qmgr', - description: - 'Name of the queue manager. Queue managers provide queuing services to applications, and manages the queues that belong to them.\n', - type: 'keyword', - }, - { - name: 'arithinsert', - description: 'Changing content based on error.id', - type: 'keyword', - }, - { - name: 'commentinsert', - description: 'Changing content based on error.id', - type: 'keyword', - }, - { - name: 'errordescription', - description: 'Please add description', - example: 'Please add example', - type: 'text', - }, - { - name: 'explanation', - description: 'Explaines the error in more detail', - type: 'keyword', - }, - { - name: 'action', - description: 'Defines what to do when the error occurs', - type: 'keyword', - }, - { - name: 'code', - description: 'Error code.', - type: 'keyword', - }, - ], - }, - ], - }, - ], - }, - { - key: 'iptables', - title: 'iptables', - description: 'Module for handling the iptables logs.\n', - fields: [ - { - name: 'iptables', - type: 'group', - description: 'Fields from the iptables logs.\n', - fields: [ - { - name: 'ether_type', - type: 'long', - description: - 'Value of the ethernet type field identifying the network layer protocol.\n', - }, - { - name: 'flow_label', - type: 'integer', - description: 'IPv6 flow label.\n', - }, - { - name: 'fragment_flags', - type: 'keyword', - description: 'IP fragment flags. A combination of CE, DF and MF.\n', - }, - { - name: 'fragment_offset', - type: 'long', - description: 'Offset of the current IP fragment.\n', - }, - { - name: 'icmp', - type: 'group', - description: 'ICMP fields.\n', - fields: [ - { - name: 'code', - type: 'long', - description: 'ICMP code.\n', - }, - { - name: 'id', - type: 'long', - description: 'ICMP ID.\n', - }, - { - name: 'parameter', - type: 'long', - description: 'ICMP parameter.\n', - }, - { - name: 'redirect', - type: 'ip', - description: 'ICMP redirect address.\n', - }, - { - name: 'seq', - type: 'long', - description: 'ICMP sequence number.\n', - }, - { - name: 'type', - type: 'long', - description: 'ICMP type.\n', - }, - ], - }, - { - name: 'id', - type: 'long', - description: 'Packet identifier.\n', - }, - { - name: 'incomplete_bytes', - type: 'long', - description: 'Number of incomplete bytes.\n', - }, - { - name: 'input_device', - type: 'keyword', - description: 'Device that received the packet.\n', - }, - { - name: 'precedence_bits', - type: 'short', - description: 'IP precedence bits.\n', - }, - { - name: 'tos', - type: 'long', - description: 'IP Type of Service field.\n', - }, - { - name: 'length', - type: 'long', - description: 'Packet length.\n', - }, - { - name: 'output_device', - type: 'keyword', - description: 'Device that output the packet.\n', - }, - { - name: 'tcp', - type: 'group', - description: 'TCP fields.\n', - fields: [ - { - name: 'flags', - type: 'keyword', - description: 'TCP flags.\n', - }, - { - name: 'reserved_bits', - type: 'short', - description: 'TCP reserved bits.\n', - }, - { - name: 'seq', - type: 'long', - description: 'TCP sequence number.\n', - }, - { - name: 'ack', - type: 'long', - description: 'TCP Acknowledgment number.\n', - }, - { - name: 'window', - type: 'long', - description: 'Advertised TCP window size.\n', - }, - ], - }, - { - name: 'ttl', - type: 'integer', - description: 'Time To Live field.\n', - }, - { - name: 'udp', - type: 'group', - description: 'UDP fields.\n', - fields: [ - { - name: 'length', - type: 'long', - description: 'Length of the UDP header and payload.\n', - }, - ], - }, - { - name: 'ubiquiti', - type: 'group', - description: 'Fields for Ubiquiti network devices.\n', - fields: [ - { - name: 'input_zone', - type: 'keyword', - description: 'Input zone.\n', - }, - { - name: 'output_zone', - type: 'keyword', - description: 'Output zone.\n', - }, - { - name: 'rule_number', - type: 'keyword', - description: 'The rule number within the rule set.', - }, - { - name: 'rule_set', - type: 'keyword', - description: 'The rule set name.', - }, - ], - }, - ], - }, - ], - }, - { - key: 'misp', - title: 'MISP', - description: 'Module for handling threat information from MISP.\n', - fields: [ - { - name: 'misp', - type: 'group', - description: 'Fields from MISP threat information.\n', - fields: [ - { - name: 'attack_pattern', - title: 'Attack Pattern', - short: 'Fields that let you store attack patterns', - description: - 'Fields provide support for specifying information about attack patterns.\n', - type: 'group', - fields: [ - { - name: 'id', - level: 'core', - type: 'keyword', - description: 'Identifier of the threat indicator.\n', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - description: 'Name of the attack pattern.\n', - }, - { - name: 'description', - level: 'extended', - type: 'text', - description: 'Description of the attack pattern.\n', - }, - { - name: 'kill_chain_phases', - level: 'extended', - type: 'keyword', - description: 'The kill chain phase(s) to which this attack pattern corresponds.\n', - }, - ], - }, - { - name: 'campaign', - title: 'Campaign', - short: 'Fields that let you store campaign information', - description: 'Fields provide support for specifying information about campaigns.\n', - type: 'group', - fields: [ - { - name: 'id', - level: 'core', - type: 'keyword', - description: 'Identifier of the campaign.\n', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - description: 'Name of the campaign.\n', - }, - { - name: 'description', - level: 'extended', - type: 'text', - description: 'Description of the campaign.\n', - }, - { - name: 'aliases', - level: 'extended', - type: 'text', - description: 'Alternative names used to identify this campaign.\n', - }, - { - name: 'first_seen', - level: 'core', - type: 'date', - description: 'The time that this Campaign was first seen, in RFC3339 format.\n', - }, - { - name: 'last_seen', - level: 'core', - type: 'date', - description: 'The time that this Campaign was last seen, in RFC3339 format.\n', - }, - { - name: 'objective', - level: 'core', - type: 'keyword', - description: - "This field defines the Campaign's primary goal, objective, desired outcome, or intended effect.\n", - }, - ], - }, - { - name: 'course_of_action', - title: 'Course of Action', - short: 'Fields that let you store information about course of action.', - description: - 'A Course of Action is an action taken either to prevent an attack or to respond to an attack that is in progress.\n', - type: 'group', - fields: [ - { - name: 'id', - level: 'core', - type: 'keyword', - description: 'Identifier of the Course of Action.\n', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - description: 'The name used to identify the Course of Action.\n', - }, - { - name: 'description', - level: 'extended', - type: 'text', - description: 'Description of the Course of Action.\n', - }, - ], - }, - { - name: 'identity', - title: 'Identity', - short: 'Fields that let you store information about identity.', - description: - 'Identity can represent actual individuals, organizations, or groups, as well as classes of individuals, organizations, or groups.\n', - type: 'group', - fields: [ - { - name: 'id', - level: 'core', - type: 'keyword', - description: 'Identifier of the Identity.\n', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - description: 'The name used to identify the Identity.\n', - }, - { - name: 'description', - level: 'extended', - type: 'text', - description: 'Description of the Identity.\n', - }, - { - name: 'identity_class', - level: 'core', - type: 'keyword', - description: - 'The type of entity that this Identity describes, e.g., an individual or organization. Open Vocab - identity-class-ov\n', - }, - { - name: 'labels', - level: 'extended', - type: 'keyword', - description: 'The list of roles that this Identity performs.\n', - example: 'CEO\n', - }, - { - name: 'sectors', - level: 'extended', - type: 'keyword', - description: - 'The list of sectors that this Identity belongs to. Open Vocab - industry-sector-ov\n', - }, - { - name: 'contact_information', - level: 'extended', - type: 'text', - description: - 'The contact information (e-mail, phone number, etc.) for this Identity.\n', - }, - ], - }, - { - name: 'intrusion_set', - title: 'Intrusion Set', - short: 'Fields that let you store information about Intrusion Set.', - description: - 'An Intrusion Set is a grouped set of adversary behavior and resources with common properties that is believed to be orchestrated by a single organization.\n', - type: 'group', - fields: [ - { - name: 'id', - level: 'core', - type: 'keyword', - description: 'Identifier of the Intrusion Set.\n', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - description: 'The name used to identify the Intrusion Set.\n', - }, - { - name: 'description', - level: 'extended', - type: 'text', - description: 'Description of the Intrusion Set.\n', - }, - { - name: 'aliases', - level: 'extended', - type: 'text', - description: 'Alternative names used to identify the Intrusion Set.\n', - }, - { - name: 'first_seen', - level: 'extended', - type: 'date', - description: - 'The time that this Intrusion Set was first seen, in RFC3339 format.\n', - }, - { - name: 'last_seen', - level: 'extended', - type: 'date', - description: 'The time that this Intrusion Set was last seen, in RFC3339 format.\n', - }, - { - name: 'goals', - level: 'extended', - type: 'text', - description: - 'The high level goals of this Intrusion Set, namely, what are they trying to do.\n', - }, - { - name: 'resource_level', - level: 'extended', - type: 'text', - description: - 'This defines the organizational level at which this Intrusion Set typically works. Open Vocab - attack-resource-level-ov\n', - }, - { - name: 'primary_motivation', - level: 'extended', - type: 'text', - description: - 'The primary reason, motivation, or purpose behind this Intrusion Set. Open Vocab - attack-motivation-ov\n', - }, - { - name: 'secondary_motivations', - level: 'extended', - type: 'text', - description: - 'The secondary reasons, motivations, or purposes behind this Intrusion Set. Open Vocab - attack-motivation-ov\n', - }, - ], - }, - { - name: 'malware', - title: 'Malware', - short: 'Fields that let you store information about Malware.', - description: - "Malware is a type of TTP that is also known as malicious code and malicious software, refers to a program that is inserted into a system, usually covertly, with the intent of compromising the confidentiality, integrity, or availability of the victim's data, applications, or operating system (OS) or of otherwise annoying or disrupting the victim.\n", - type: 'group', - fields: [ - { - name: 'id', - level: 'core', - type: 'keyword', - description: 'Identifier of the Malware.\n', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - description: 'The name used to identify the Malware.\n', - }, - { - name: 'description', - level: 'extended', - type: 'text', - description: 'Description of the Malware.\n', - }, - { - name: 'labels', - level: 'core', - type: 'keyword', - description: - 'The type of malware being described. Open Vocab - malware-label-ov. adware,backdoor,bot,ddos,dropper,exploit-kit,keylogger,ransomware, remote-access-trojan,resource-exploitation,rogue-security-software,rootkit, screen-capture,spyware,trojan,virus,worm\n', - }, - { - name: 'kill_chain_phases', - format: 'string', - level: 'extended', - type: 'keyword', - description: - 'The list of kill chain phases for which this Malware instance can be used.\n', - }, - ], - }, - { - name: 'note', - title: 'Note', - short: 'Fields that let you store information about Malware.', - description: - 'A Note is a comment or note containing informative text to help explain the context of one or more STIX Objects (SDOs or SROs) or to provide additional analysis that is not contained in the original object.\n', - type: 'group', - fields: [ - { - name: 'id', - level: 'core', - type: 'keyword', - description: 'Identifier of the Note.\n', - }, - { - name: 'summary', - level: 'extended', - type: 'keyword', - description: 'A brief description used as a summary of the Note.\n', - }, - { - name: 'description', - level: 'extended', - type: 'text', - description: 'The content of the Note.\n', - }, - { - name: 'authors', - level: 'extended', - type: 'keyword', - description: 'The name of the author(s) of this Note.\n', - }, - { - name: 'object_refs', - level: 'extended', - type: 'keyword', - description: - 'The STIX Objects (SDOs and SROs) that the note is being applied to.\n', - }, - ], - }, - { - name: 'threat_indicator', - title: 'Threat Indicator', - short: 'Fields that let you store Threat Indicators', - description: - 'Fields provide support for specifying information about threat indicators, and related matching patterns.\n', - type: 'group', - fields: [ - { - name: 'labels', - level: 'core', - type: 'keyword', - description: 'list of type open-vocab that specifies the type of indicator.\n', - example: 'Domain Watchlist\n', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - description: 'Identifier of the threat indicator.\n', - }, - { - name: 'version', - level: 'core', - type: 'keyword', - description: 'Version of the threat indicator.\n', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - description: 'Type of the threat indicator.\n', - }, - { - name: 'description', - level: 'core', - type: 'text', - description: 'Description of the threat indicator.\n', - }, - { - name: 'feed', - level: 'core', - type: 'text', - description: 'Name of the threat feed.\n', - }, - { - name: 'valid_from', - level: 'core', - type: 'date', - description: - 'The time from which this Indicator should be considered valuable intelligence, in RFC3339 format.\n', - }, - { - name: 'valid_until', - level: 'core', - type: 'date', - description: - 'The time at which this Indicator should no longer be considered valuable intelligence. If the valid_until property is omitted, then there is no constraint on the latest time for which the indicator should be used, in RFC3339 format.\n', - }, - { - name: 'severity', - format: 'string', - level: 'core', - type: 'keyword', - description: 'Threat severity to which this indicator corresponds.\n', - example: 'high', - }, - { - name: 'confidence', - level: 'core', - type: 'keyword', - description: 'Confidence level to which this indicator corresponds.\n', - example: 'high', - }, - { - name: 'kill_chain_phases', - format: 'string', - level: 'extended', - type: 'keyword', - description: 'The kill chain phase(s) to which this indicator corresponds.\n', - }, - { - name: 'mitre_tactic', - format: 'string', - level: 'extended', - type: 'keyword', - description: 'MITRE tactics to which this indicator corresponds.\n', - example: 'Initial Access', - }, - { - name: 'mitre_technique', - format: 'string', - level: 'extended', - type: 'keyword', - description: 'MITRE techniques to which this indicator corresponds.\n', - example: 'Drive-by Compromise', - }, - { - name: 'attack_pattern', - level: 'core', - type: 'keyword', - description: - 'The attack_pattern for this indicator is a STIX Pattern as specified in STIX Version 2.0 Part 5 - STIX Patterning.\n', - example: "[destination:ip = '91.219.29.188/32']\n", - }, - { - name: 'attack_pattern_kql', - level: 'core', - type: 'keyword', - description: - 'The attack_pattern for this indicator is KQL query that matches the attack_pattern specified in the STIX Pattern format.\n', - example: 'destination.ip: "91.219.29.188/32"\n', - }, - { - name: 'negate', - level: 'core', - type: 'boolean', - description: 'When set to true, it specifies the absence of the attack_pattern.\n', - }, - { - name: 'intrusion_set', - level: 'extended', - type: 'keyword', - description: 'Name of the intrusion set if known.\n', - }, - { - name: 'campaign', - level: 'extended', - type: 'keyword', - description: 'Name of the attack campaign if known.\n', - }, - { - name: 'threat_actor', - level: 'extended', - type: 'keyword', - description: 'Name of the threat actor if known.\n', - }, - ], - }, - { - name: 'observed_data', - title: 'Observed Data', - short: 'Fields that let you store information about Observed Data.', - description: - 'Observed data conveys information that was observed on systems and networks, such as log data or network traffic, using the Cyber Observable specification.\n', - type: 'group', - fields: [ - { - name: 'id', - level: 'core', - type: 'keyword', - description: 'Identifier of the Observed Data.\n', - }, - { - name: 'first_observed', - level: 'core', - type: 'date', - description: - 'The beginning of the time window that the data was observed, in RFC3339 format.\n', - }, - { - name: 'last_observed', - level: 'core', - type: 'date', - description: - 'The end of the time window that the data was observed, in RFC3339 format.\n', - }, - { - name: 'number_observed', - level: 'core', - type: 'integer', - description: - 'The number of times the data represented in the objects property was observed. This MUST be an integer between 1 and 999,999,999 inclusive.\n', - }, - { - name: 'objects', - level: 'core', - type: 'keyword', - description: - 'A dictionary of Cyber Observable Objects that describes the single fact that was observed.\n', - }, - ], - }, - { - name: 'report', - title: 'Report', - short: 'Fields that let you store information about Report.', - description: - 'Reports are collections of threat intelligence focused on one or more topics, such as a description of a threat actor, malware, or attack technique, including context and related details.\n', - type: 'group', - fields: [ - { - name: 'id', - level: 'core', - type: 'keyword', - description: 'Identifier of the Report.\n', - }, - { - name: 'labels', - level: 'core', - type: 'keyword', - description: - 'This field is an Open Vocabulary that specifies the primary subject of this report. Open Vocab - report-label-ov. threat-report,attack-pattern,campaign,identity,indicator,malware,observed-data,threat-actor,tool,vulnerability\n', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - description: 'The name used to identify the Report.\n', - }, - { - name: 'description', - level: 'extended', - type: 'text', - description: 'A description that provides more details and context about Report.\n', - }, - { - name: 'published', - level: 'extended', - type: 'date', - description: - 'The date that this report object was officially published by the creator of this report, in RFC3339 format.\n', - }, - { - name: 'object_refs', - level: 'core', - type: 'text', - description: 'Specifies the STIX Objects that are referred to by this Report.\n', - }, - ], - }, - { - name: 'threat_actor', - title: 'Threat Actor', - short: 'Fields that let you store information about Threat Actor.', - description: - 'Threat Actors are actual individuals, groups, or organizations believed to be operating with malicious intent.\n', - type: 'group', - fields: [ - { - name: 'id', - level: 'core', - type: 'keyword', - description: 'Identifier of the Threat Actor.\n', - }, - { - name: 'labels', - level: 'core', - type: 'keyword', - description: - 'This field specifies the type of threat actor. Open Vocab - threat-actor-label-ov. activist,competitor,crime-syndicate,criminal,hacker,insider-accidental,insider-disgruntled,nation-state,sensationalist,spy,terrorist\n', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - description: 'The name used to identify this Threat Actor or Threat Actor group.\n', - }, - { - name: 'description', - level: 'extended', - type: 'text', - description: - 'A description that provides more details and context about the Threat Actor.\n', - }, - { - name: 'aliases', - level: 'extended', - type: 'text', - description: 'A list of other names that this Threat Actor is believed to use.\n', - }, - { - name: 'roles', - level: 'extended', - type: 'text', - description: - 'This is a list of roles the Threat Actor plays. Open Vocab - threat-actor-role-ov. agent,director,independent,sponsor,infrastructure-operator,infrastructure-architect,malware-author\n', - }, - { - name: 'goals', - level: 'extended', - type: 'text', - description: - 'The high level goals of this Threat Actor, namely, what are they trying to do.\n', - }, - { - name: 'sophistication', - level: 'extended', - type: 'text', - description: - 'The skill, specific knowledge, special training, or expertise a Threat Actor must have to perform the attack. Open Vocab - threat-actor-sophistication-ov. none,minimal,intermediate,advanced,strategic,expert,innovator\n', - }, - { - name: 'resource_level', - level: 'extended', - type: 'text', - description: - 'This defines the organizational level at which this Threat Actor typically works. Open Vocab - attack-resource-level-ov. individual,club,contest,team,organization,government\n', - }, - { - name: 'primary_motivation', - level: 'extended', - type: 'text', - description: - 'The primary reason, motivation, or purpose behind this Threat Actor. Open Vocab - attack-motivation-ov. accidental,coercion,dominance,ideology,notoriety,organizational-gain,personal-gain,personal-satisfaction,revenge,unpredictable\n', - }, - { - name: 'secondary_motivations', - level: 'extended', - type: 'text', - description: - 'The secondary reasons, motivations, or purposes behind this Threat Actor. Open Vocab - attack-motivation-ov. accidental,coercion,dominance,ideology,notoriety,organizational-gain,personal-gain,personal-satisfaction,revenge,unpredictable\n', - }, - { - name: 'personal_motivations', - level: 'extended', - type: 'text', - description: - 'The personal reasons, motivations, or purposes of the Threat Actor regardless of organizational goals. Open Vocab - attack-motivation-ov. accidental,coercion,dominance,ideology,notoriety,organizational-gain,personal-gain,personal-satisfaction,revenge,unpredictable\n', - }, - ], - }, - { - name: 'tool', - title: 'Tool', - short: 'Fields that let you store information about Tool.', - description: - 'Tools are legitimate software that can be used by threat actors to perform attacks.\n', - type: 'group', - fields: [ - { - name: 'id', - level: 'core', - type: 'keyword', - description: 'Identifier of the Tool.\n', - }, - { - name: 'labels', - level: 'core', - type: 'keyword', - description: - 'The kind(s) of tool(s) being described. Open Vocab - tool-label-ov. denial-of-service,exploitation,information-gathering,network-capture,credential-exploitation,remote-access,vulnerability-scanning\n', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - description: 'The name used to identify the Tool.\n', - }, - { - name: 'description', - level: 'extended', - type: 'text', - description: - 'A description that provides more details and context about the Tool.\n', - }, - { - name: 'tool_version', - level: 'extended', - type: 'keyword', - description: 'The version identifier associated with the Tool.\n', - }, - { - name: 'kill_chain_phases', - level: 'extended', - type: 'text', - description: - 'The list of kill chain phases for which this Tool instance can be used.\n', - }, - ], - }, - { - name: 'vulnerability', - title: 'Vulnerability', - short: 'Fields that let you store information about Vulnerability.', - description: - 'A Vulnerability is a mistake in software that can be directly used by a hacker to gain access to a system or network.\n', - type: 'group', - fields: [ - { - name: 'id', - level: 'core', - type: 'keyword', - description: 'Identifier of the Vulnerability.\n', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - description: 'The name used to identify the Vulnerability.\n', - }, - { - name: 'description', - level: 'extended', - type: 'text', - description: - 'A description that provides more details and context about the Vulnerability.\n', - }, - ], - }, - ], - }, - ], - }, - { - key: 'mssql', - title: 'mssql', - description: 'MS SQL Filebeat Module', - fields: [ - { - name: 'mssql', - type: 'group', - description: 'Fields from the MSSQL log files', - fields: [ - { - name: 'log', - description: 'Common log fields', - type: 'group', - fields: [ - { - name: 'origin', - description: - 'Origin of the message, usually the server but it can also be a recovery process', - type: 'keyword', - }, - ], - }, - ], - }, - ], - }, - { - key: 'netflow-module', - title: 'NetFlow', - description: - 'Module for receiving NetFlow and IPFIX flow records over UDP. The module does not add fields beyond what the netflow input provides.\n', - fields: [], - }, - { - key: 'o365', - title: 'Office 365', - description: 'Module for handling logs from Office 365.\n', - fields: [ - { - name: 'o365.audit', - type: 'group', - default_field: false, - description: 'Fields from Office 365 Management API audit logs.\n', - fields: [ - { - name: 'Actor', - type: 'array', - fields: [ - { - name: 'ID', - type: 'keyword', - }, - { - name: 'Type', - type: 'keyword', - }, - ], - }, - { - name: 'ActorContextId', - type: 'keyword', - }, - { - name: 'ActorIpAddress', - type: 'keyword', - }, - { - name: 'ActorUserId', - type: 'keyword', - }, - { - name: 'ActorYammerUserId', - type: 'keyword', - }, - { - name: 'AlertEntityId', - type: 'keyword', - }, - { - name: 'AlertId', - type: 'keyword', - }, - { - name: 'AlertLinks', - type: 'array', - }, - { - name: 'AlertType', - type: 'keyword', - }, - { - name: 'AppId', - type: 'keyword', - }, - { - name: 'ApplicationDisplayName', - type: 'keyword', - }, - { - name: 'ApplicationId', - type: 'keyword', - }, - { - name: 'AzureActiveDirectoryEventType', - type: 'keyword', - }, - { - name: 'ExchangeMetaData.*', - type: 'object', - }, - { - name: 'Category', - type: 'keyword', - }, - { - name: 'ClientAppId', - type: 'keyword', - }, - { - name: 'ClientInfoString', - type: 'keyword', - }, - { - name: 'ClientIP', - type: 'keyword', - }, - { - name: 'ClientIPAddress', - type: 'keyword', - }, - { - name: 'Comments', - type: 'text', - norms: false, - }, - { - name: 'CorrelationId', - type: 'keyword', - }, - { - name: 'CreationTime', - type: 'keyword', - }, - { - name: 'CustomUniqueId', - type: 'keyword', - }, - { - name: 'Data', - type: 'keyword', - }, - { - name: 'DataType', - type: 'keyword', - }, - { - name: 'EntityType', - type: 'keyword', - }, - { - name: 'EventData', - type: 'keyword', - }, - { - name: 'EventSource', - type: 'keyword', - }, - { - name: 'ExceptionInfo.*', - type: 'object', - }, - { - name: 'ExtendedProperties.*', - type: 'object', - }, - { - name: 'ExternalAccess', - type: 'keyword', - }, - { - name: 'GroupName', - type: 'keyword', - }, - { - name: 'Id', - type: 'keyword', - }, - { - name: 'ImplicitShare', - type: 'keyword', - }, - { - name: 'IncidentId', - type: 'keyword', - }, - { - name: 'InternalLogonType', - type: 'keyword', - }, - { - name: 'InterSystemsId', - type: 'keyword', - }, - { - name: 'IntraSystemId', - type: 'keyword', - }, - { - name: 'Item.*', - type: 'object', - }, - { - name: 'Item.*.*', - type: 'object', - }, - { - name: 'ItemName', - type: 'keyword', - }, - { - name: 'ItemType', - type: 'keyword', - }, - { - name: 'ListId', - type: 'keyword', - }, - { - name: 'ListItemUniqueId', - type: 'keyword', - }, - { - name: 'LogonError', - type: 'keyword', - }, - { - name: 'LogonType', - type: 'keyword', - }, - { - name: 'LogonUserSid', - type: 'keyword', - }, - { - name: 'MailboxGuid', - type: 'keyword', - }, - { - name: 'MailboxOwnerMasterAccountSid', - type: 'keyword', - }, - { - name: 'MailboxOwnerSid', - type: 'keyword', - }, - { - name: 'MailboxOwnerUPN', - type: 'keyword', - }, - { - name: 'Members', - type: 'array', - }, - { - name: 'Members.*', - type: 'object', - }, - { - name: 'ModifiedProperties.*.*', - type: 'object', - }, - { - name: 'Name', - type: 'keyword', - }, - { - name: 'ObjectId', - type: 'keyword', - }, - { - name: 'Operation', - type: 'keyword', - }, - { - name: 'OrganizationId', - type: 'keyword', - }, - { - name: 'OrganizationName', - type: 'keyword', - }, - { - name: 'OriginatingServer', - type: 'keyword', - }, - { - name: 'Parameters.*', - type: 'object', - }, - { - name: 'PolicyDetails', - type: 'array', - }, - { - name: 'PolicyId', - type: 'keyword', - }, - { - name: 'RecordType', - type: 'keyword', - }, - { - name: 'ResultStatus', - type: 'keyword', - }, - { - name: 'SensitiveInfoDetectionIsIncluded', - type: 'keyword', - }, - { - name: 'SharePointMetaData.*', - type: 'object', - }, - { - name: 'SessionId', - type: 'keyword', - }, - { - name: 'Severity', - type: 'keyword', - }, - { - name: 'Site', - type: 'keyword', - }, - { - name: 'SiteUrl', - type: 'keyword', - }, - { - name: 'Source', - type: 'keyword', - }, - { - name: 'SourceFileExtension', - type: 'keyword', - }, - { - name: 'SourceFileName', - type: 'keyword', - }, - { - name: 'SourceRelativeUrl', - type: 'keyword', - }, - { - name: 'Status', - type: 'keyword', - }, - { - name: 'SupportTicketId', - type: 'keyword', - }, - { - name: 'Target', - type: 'array', - fields: [ - { - name: 'ID', - type: 'keyword', - }, - { - name: 'Type', - type: 'keyword', - }, - ], - }, - { - name: 'TargetContextId', - type: 'keyword', - }, - { - name: 'TargetUserOrGroupName', - type: 'keyword', - }, - { - name: 'TargetUserOrGroupType', - type: 'keyword', - }, - { - name: 'TeamName', - type: 'keyword', - }, - { - name: 'TeamGuid', - type: 'keyword', - }, - { - name: 'UniqueSharingId', - type: 'keyword', - }, - { - name: 'UserAgent', - type: 'keyword', - }, - { - name: 'UserId', - type: 'keyword', - }, - { - name: 'UserKey', - type: 'keyword', - }, - { - name: 'UserType', - type: 'keyword', - }, - { - name: 'Version', - type: 'keyword', - }, - { - name: 'WebId', - type: 'keyword', - }, - { - name: 'Workload', - type: 'keyword', - }, - { - name: 'YammerNetworkId', - type: 'keyword', - }, - ], - }, - ], - }, - { - key: 'okta', - title: 'Okta', - description: 'Module for handling system logs from Okta.\n', - fields: [ - { - name: 'okta', - type: 'group', - default_field: false, - description: 'Fields from Okta.\n', - fields: [ - { - name: 'uuid', - title: 'UUID', - short: 'The unique identifier of the Okta LogEvent.', - description: 'The unique identifier of the Okta LogEvent.\n', - type: 'keyword', - }, - { - name: 'event_type', - title: 'Event Type', - short: 'The type of the LogEvent.', - description: 'The type of the LogEvent.\n', - type: 'keyword', - }, - { - name: 'version', - title: 'Version', - short: 'The version of the LogEvent.', - description: 'The version of the LogEvent.\n', - type: 'keyword', - }, - { - name: 'severity', - title: 'Severity', - short: 'The severity of the LogEvent.', - description: - 'The severity of the LogEvent. Must be one of DEBUG, INFO, WARN, or ERROR.\n', - type: 'keyword', - }, - { - name: 'display_message', - title: 'Display Message', - short: 'The display message of the LogEvent.', - description: 'The display message of the LogEvent.\n', - type: 'keyword', - }, - { - name: 'actor', - title: 'Actor', - short: 'Fields of the actor for the LogEvent.', - description: 'Fields that let you store information of the actor for the LogEvent.\n', - type: 'group', - fields: [ - { - name: 'id', - type: 'keyword', - description: 'Identifier of the actor.\n', - }, - { - name: 'type', - type: 'keyword', - description: 'Type of the actor.\n', - }, - { - name: 'alternate_id', - type: 'keyword', - description: 'Alternate identifier of the actor.\n', - }, - { - name: 'display_name', - type: 'keyword', - description: 'Display name of the actor.\n', - }, - ], - }, - { - name: 'client', - title: 'Client', - short: 'Fields about the client of the actor.', - description: 'Fields that let you store information about the client of the actor.\n', - type: 'group', - fields: [ - { - name: 'ip', - type: 'ip', - description: 'The IP address of the client.\n', - }, - { - name: 'user_agent', - description: 'Fields about the user agent information of the client.\n', - type: 'group', - fields: [ - { - name: 'raw_user_agent', - type: 'keyword', - description: 'The raw informaton of the user agent.\n', - }, - { - name: 'os', - type: 'keyword', - description: 'The OS informaton.\n', - }, - { - name: 'browser', - type: 'keyword', - description: 'The browser informaton of the client.\n', - }, - ], - }, - { - name: 'zone', - type: 'keyword', - description: 'The zone information of the client.\n', - }, - { - name: 'device', - type: 'keyword', - description: 'The information of the client device.\n', - }, - { - name: 'id', - type: 'keyword', - description: 'The identifier of the client.\n', - }, - ], - }, - { - name: 'outcome', - title: 'Outcome of the LogEvent.', - short: 'Fields that let you store information about the outcome.', - description: 'Fields that let you store information about the outcome.\n', - type: 'group', - fields: [ - { - name: 'reason', - type: 'keyword', - description: 'The reason of the outcome.\n', - }, - { - name: 'result', - type: 'keyword', - description: - 'The result of the outcome. Must be one of: SUCCESS, FAILURE, SKIPPED, ALLOW, DENY, CHALLENGE, UNKNOWN.\n', - }, - ], - }, - { - name: 'target', - title: 'Target', - short: 'The list of targets.', - description: 'The list of targets.\n', - type: 'array', - fields: [ - { - name: 'id', - type: 'keyword', - description: 'Identifier of the actor.\n', - }, - { - name: 'type', - type: 'keyword', - description: 'Type of the actor.\n', - }, - { - name: 'alternate_id', - type: 'keyword', - description: 'Alternate identifier of the actor.\n', - }, - { - name: 'display_name', - type: 'keyword', - description: 'Display name of the actor.\n', - }, - ], - }, - { - name: 'transaction', - title: 'Transaction', - short: 'Fields that let you store information about related transaction.', - description: 'Fields that let you store information about related transaction.\n', - type: 'group', - fields: [ - { - name: 'id', - type: 'keyword', - description: 'Identifier of the transaction.\n', - }, - { - name: 'type', - type: 'keyword', - description: 'The type of transaction. Must be one of "WEB", "JOB".\n', - }, - ], - }, - { - name: 'debug_context', - title: 'Debug Context', - short: 'Fields that let you store information about the debug context.', - description: 'Fields that let you store information about the debug context.\n', - type: 'group', - fields: [ - { - name: 'debug_data', - description: 'The debug data.\n', - type: 'group', - fields: [ - { - name: 'device_fingerprint', - type: 'keyword', - description: 'The fingerprint of the device.\n', - }, - { - name: 'request_id', - type: 'keyword', - description: 'The identifier of the request.\n', - }, - { - name: 'request_uri', - type: 'keyword', - description: 'The request URI.\n', - }, - { - name: 'threat_suspected', - type: 'keyword', - description: 'Threat suspected.\n', - }, - { - name: 'url', - type: 'keyword', - description: 'The URL.\n', - }, - ], - }, - ], - }, - { - name: 'authentication_context', - title: 'Authentication Context', - short: 'Fields that let you store information about authentication context.', - description: 'Fields that let you store information about authentication context.\n', - type: 'group', - fields: [ - { - name: 'authentication_provider', - type: 'keyword', - description: - 'The information about the authentication provider. Must be one of OKTA_AUTHENTICATION_PROVIDER, ACTIVE_DIRECTORY, LDAP, FEDERATION, SOCIAL, FACTOR_PROVIDER.\n', - }, - { - name: 'authentication_step', - type: 'integer', - description: 'The authentication step.\n', - }, - { - name: 'credential_provider', - type: 'keyword', - description: - 'The information about credential provider. Must be one of OKTA_CREDENTIAL_PROVIDER, RSA, SYMANTEC, GOOGLE, DUO, YUBIKEY.\n', - }, - { - name: 'credential_type', - type: 'keyword', - description: - 'The information about credential type. Must be one of OTP, SMS, PASSWORD, ASSERTION, IWA, EMAIL, OAUTH2, JWT, CERTIFICATE, PRE_SHARED_SYMMETRIC_KEY, OKTA_CLIENT_SESSION, DEVICE_UDID.\n', - }, - { - name: 'issuer', - description: 'The information about the issuer.\n', - type: 'array', - fields: [ - { - name: 'id', - type: 'keyword', - description: 'The identifier of the issuer.\n', - }, - { - name: 'type', - type: 'keyword', - description: 'The type of the issuer.\n', - }, - ], - }, - { - name: 'external_session_id', - type: 'keyword', - description: 'The session identifer of the external session if any.\n', - }, - { - name: 'interface', - type: 'keyword', - description: 'The interface used. e.g., Outlook, Office365, wsTrust\n', - }, - ], - }, - { - name: 'security_context', - title: 'Security Context', - short: 'Fields that let you store information about security context.', - description: 'Fields that let you store information about security context.\n', - type: 'group', - fields: [ - { - name: 'as', - type: 'group', - description: 'The autonomous system.\n', - fields: [ - { - name: 'number', - type: 'integer', - description: 'The AS number.\n', - }, - { - name: 'organization', - type: 'group', - description: 'The organization that owns the AS number.\n', - fields: [ - { - name: 'name', - type: 'keyword', - description: 'The organization name.\n', - }, - ], - }, - ], - }, - { - name: 'isp', - type: 'keyword', - description: 'The Internet Service Provider.\n', - }, - { - name: 'domain', - type: 'keyword', - description: 'The domain name.\n', - }, - { - name: 'is_proxy', - type: 'boolean', - description: 'Whether it is a proxy or not.\n', - }, - ], - }, - { - name: 'request', - title: 'Request', - short: 'Fields that let you store information about the request.', - description: - 'Fields that let you store information about the request, in the form of list of ip_chain.\n', - type: 'group', - fields: [ - { - name: 'ip_chain', - description: 'List of ip_chain objects.\n', - type: 'group', - fields: [ - { - name: 'ip', - type: 'ip', - description: 'IP address.\n', - }, - { - name: 'version', - type: 'keyword', - description: 'IP version. Must be one of V4, V6.\n', - }, - { - name: 'source', - type: 'keyword', - description: 'Source information.\n', - }, - { - name: 'geographical_context', - description: 'Geographical information.\n', - type: 'group', - fields: [ - { - name: 'city', - type: 'keyword', - description: 'The city.', - }, - { - name: 'state', - type: 'keyword', - description: 'The state.', - }, - { - name: 'postal_code', - type: 'keyword', - description: 'The postal code.', - }, - { - name: 'country', - type: 'keyword', - description: 'The country.', - }, - { - name: 'geolocation', - description: 'Geolocation information.\n', - type: 'geo_point', - }, - ], - }, - ], - }, - ], - }, - ], - }, - ], - }, - { - key: 'panw', - title: 'panw', - description: 'Module for Palo Alto Networks (PAN-OS)\n', - fields: [ - { - name: 'panw', - type: 'group', - description: 'Fields from the panw module.\n', - fields: [ - { - name: 'panos', - type: 'group', - description: 'Fields for the Palo Alto Networks PAN-OS logs.\n', - fields: [ - { - name: 'ruleset', - type: 'keyword', - description: 'Name of the rule that matched this session.\n', - }, - { - name: 'source', - type: 'group', - description: 'Fields to extend the top-level source object.\n', - fields: [ - { - name: 'zone', - type: 'keyword', - description: 'Source zone for this session.\n', - }, - { - name: 'interface', - type: 'keyword', - description: 'Source interface for this session.\n', - }, - { - name: 'nat', - type: 'group', - description: 'Post-NAT source address, if source NAT is performed.\n', - fields: [ - { - name: 'ip', - type: 'ip', - description: 'Post-NAT source IP.\n', - }, - { - name: 'port', - type: 'long', - description: 'Post-NAT source port.\n', - }, - ], - }, - ], - }, - { - name: 'destination', - type: 'group', - description: 'Fields to extend the top-level destination object.\n', - fields: [ - { - name: 'zone', - type: 'keyword', - description: 'Destination zone for this session.\n', - }, - { - name: 'interface', - type: 'keyword', - description: 'Destination interface for this session.\n', - }, - { - name: 'nat', - type: 'group', - description: 'Post-NAT destination address, if destination NAT is performed.\n', - fields: [ - { - name: 'ip', - type: 'ip', - description: 'Post-NAT destination IP.\n', - }, - { - name: 'port', - type: 'long', - description: 'Post-NAT destination port.\n', - }, - ], - }, - ], - }, - { - name: 'network', - type: 'group', - description: 'Fields to extend the top-level network object.\n', - fields: [ - { - name: 'pcap_id', - type: 'keyword', - description: 'Packet capture ID for a threat.\n', - }, - { - name: 'nat', - type: 'group', - fields: [ - { - name: 'community_id', - type: 'keyword', - description: 'Community ID flow-hash for the NAT 5-tuple.\n', - }, - ], - }, - ], - }, - { - name: 'file', - type: 'group', - description: 'Fields to extend the top-level file object.\n', - fields: [ - { - name: 'hash', - description: - 'Binary hash for a threat file sent to be analyzed by the WildFire service.\n', - type: 'keyword', - }, - ], - }, - { - name: 'url', - type: 'group', - description: 'Fields to extend the top-level url object.\n', - fields: [ - { - name: 'category', - type: 'keyword', - description: - "For threat URLs, it's the URL category. For WildFire, the verdict on the file and is either 'malicious', 'grayware', or 'benign'.\n", - }, - ], - }, - { - name: 'flow_id', - type: 'keyword', - description: 'Internal numeric identifier for each session.\n', - }, - { - name: 'sequence_number', - type: 'long', - description: - 'Log entry identifier that is incremented sequentially. Unique for each log type.\n', - }, - { - name: 'threat.resource', - type: 'keyword', - description: 'URL or file name for a threat.\n', - }, - { - name: 'threat.id', - type: 'keyword', - description: 'Palo Alto Networks identifier for the threat.\n', - }, - { - name: 'threat.name', - type: 'keyword', - description: 'Palo Alto Networks name for the threat.\n', - }, - ], - }, - ], - }, - ], - }, - { - key: 'rabbitmq', - title: 'RabbitMQ', - description: 'RabbitMQ Module\n', - fields: [ - { - name: 'rabbitmq', - type: 'group', - description: '\n', - fields: [ - { - name: 'log', - type: 'group', - description: 'RabbitMQ log files\n', - fields: [ - { - name: 'pid', - type: 'keyword', - description: 'The Erlang process id', - example: '<0.222.0>', - }, - ], - }, - ], - }, - ], - }, - { - key: 'suricata', - title: 'Suricata', - description: 'Module for handling the EVE JSON logs produced by Suricata.\n', - fields: [ - { - name: 'suricata', - type: 'group', - description: 'Fields from the Suricata EVE log file.\n', - fields: [ - { - name: 'eve', - type: 'group', - description: 'Fields exported by the EVE JSON logs\n', - fields: [ - { - name: 'event_type', - type: 'keyword', - }, - { - name: 'app_proto_orig', - type: 'keyword', - }, - { - name: 'tcp', - type: 'group', - fields: [ - { - name: 'tcp_flags', - type: 'keyword', - }, - { - name: 'psh', - type: 'boolean', - }, - { - name: 'tcp_flags_tc', - type: 'keyword', - }, - { - name: 'ack', - type: 'boolean', - }, - { - name: 'syn', - type: 'boolean', - }, - { - name: 'state', - type: 'keyword', - }, - { - name: 'tcp_flags_ts', - type: 'keyword', - }, - { - name: 'rst', - type: 'boolean', - }, - { - name: 'fin', - type: 'boolean', - }, - ], - }, - { - name: 'fileinfo', - type: 'group', - fields: [ - { - name: 'sha1', - type: 'keyword', - }, - { - name: 'filename', - type: 'alias', - path: 'file.path', - }, - { - name: 'tx_id', - type: 'long', - }, - { - name: 'state', - type: 'keyword', - }, - { - name: 'stored', - type: 'boolean', - }, - { - name: 'gaps', - type: 'boolean', - }, - { - name: 'sha256', - type: 'keyword', - }, - { - name: 'md5', - type: 'keyword', - }, - { - name: 'size', - type: 'alias', - path: 'file.size', - }, - ], - }, - { - name: 'icmp_type', - type: 'long', - }, - { - name: 'dest_port', - type: 'alias', - path: 'destination.port', - }, - { - name: 'src_port', - type: 'alias', - path: 'source.port', - }, - { - name: 'proto', - type: 'alias', - path: 'network.transport', - }, - { - name: 'pcap_cnt', - type: 'long', - }, - { - name: 'src_ip', - type: 'alias', - path: 'source.ip', - }, - { - name: 'dns', - type: 'group', - fields: [ - { - name: 'type', - type: 'keyword', - }, - { - name: 'rrtype', - type: 'keyword', - }, - { - name: 'rrname', - type: 'keyword', - }, - { - name: 'rdata', - type: 'keyword', - }, - { - name: 'tx_id', - type: 'long', - }, - { - name: 'ttl', - type: 'long', - }, - { - name: 'rcode', - type: 'keyword', - }, - { - name: 'id', - type: 'long', - }, - ], - }, - { - name: 'flow_id', - type: 'keyword', - }, - { - name: 'email', - type: 'group', - fields: [ - { - name: 'status', - type: 'keyword', - }, - ], - }, - { - name: 'dest_ip', - type: 'alias', - path: 'destination.ip', - }, - { - name: 'icmp_code', - type: 'long', - }, - { - name: 'http', - type: 'group', - fields: [ - { - name: 'status', - type: 'alias', - path: 'http.response.status_code', - }, - { - name: 'redirect', - type: 'keyword', - }, - { - name: 'http_user_agent', - type: 'alias', - path: 'user_agent.original', - }, - { - name: 'protocol', - type: 'keyword', - }, - { - name: 'http_refer', - type: 'alias', - path: 'http.request.referrer', - }, - { - name: 'url', - type: 'alias', - path: 'url.original', - }, - { - name: 'hostname', - type: 'alias', - path: 'url.domain', - }, - { - name: 'length', - type: 'alias', - path: 'http.response.body.bytes', - }, - { - name: 'http_method', - type: 'alias', - path: 'http.request.method', - }, - { - name: 'http_content_type', - type: 'keyword', - }, - ], - }, - { - name: 'timestamp', - type: 'alias', - path: '@timestamp', - }, - { - name: 'in_iface', - type: 'keyword', - }, - { - name: 'alert', - type: 'group', - fields: [ - { - name: 'category', - type: 'keyword', - }, - { - name: 'severity', - type: 'alias', - path: 'event.severity', - }, - { - name: 'rev', - type: 'long', - }, - { - name: 'gid', - type: 'long', - }, - { - name: 'signature', - type: 'keyword', - }, - { - name: 'action', - type: 'alias', - path: 'event.outcome', - }, - { - name: 'signature_id', - type: 'long', - }, - ], - }, - { - name: 'ssh', - type: 'group', - fields: [ - { - name: 'client', - type: 'group', - fields: [ - { - name: 'proto_version', - type: 'keyword', - }, - { - name: 'software_version', - type: 'keyword', - }, - ], - }, - { - name: 'server', - type: 'group', - fields: [ - { - name: 'proto_version', - type: 'keyword', - }, - { - name: 'software_version', - type: 'keyword', - }, - ], - }, - ], - }, - { - name: 'stats', - type: 'group', - fields: [ - { - name: 'capture', - type: 'group', - fields: [ - { - name: 'kernel_packets', - type: 'long', - }, - { - name: 'kernel_drops', - type: 'long', - }, - { - name: 'kernel_ifdrops', - type: 'long', - }, - ], - }, - { - name: 'uptime', - type: 'long', - }, - { - name: 'detect', - type: 'group', - fields: [ - { - name: 'alert', - type: 'long', - }, - ], - }, - { - name: 'http', - type: 'group', - fields: [ - { - name: 'memcap', - type: 'long', - }, - { - name: 'memuse', - type: 'long', - }, - ], - }, - { - name: 'file_store', - type: 'group', - fields: [ - { - name: 'open_files', - type: 'long', - }, - ], - }, - { - name: 'defrag', - type: 'group', - fields: [ - { - name: 'max_frag_hits', - type: 'long', - }, - { - name: 'ipv4', - type: 'group', - fields: [ - { - name: 'timeouts', - type: 'long', - }, - { - name: 'fragments', - type: 'long', - }, - { - name: 'reassembled', - type: 'long', - }, - ], - }, - { - name: 'ipv6', - type: 'group', - fields: [ - { - name: 'timeouts', - type: 'long', - }, - { - name: 'fragments', - type: 'long', - }, - { - name: 'reassembled', - type: 'long', - }, - ], - }, - ], - }, - { - name: 'flow', - type: 'group', - fields: [ - { - name: 'tcp_reuse', - type: 'long', - }, - { - name: 'udp', - type: 'long', - }, - { - name: 'memcap', - type: 'long', - }, - { - name: 'emerg_mode_entered', - type: 'long', - }, - { - name: 'emerg_mode_over', - type: 'long', - }, - { - name: 'tcp', - type: 'long', - }, - { - name: 'icmpv6', - type: 'long', - }, - { - name: 'icmpv4', - type: 'long', - }, - { - name: 'spare', - type: 'long', - }, - { - name: 'memuse', - type: 'long', - }, - ], - }, - { - name: 'tcp', - type: 'group', - fields: [ - { - name: 'pseudo_failed', - type: 'long', - }, - { - name: 'ssn_memcap_drop', - type: 'long', - }, - { - name: 'insert_data_overlap_fail', - type: 'long', - }, - { - name: 'sessions', - type: 'long', - }, - { - name: 'pseudo', - type: 'long', - }, - { - name: 'synack', - type: 'long', - }, - { - name: 'insert_data_normal_fail', - type: 'long', - }, - { - name: 'syn', - type: 'long', - }, - { - name: 'memuse', - type: 'long', - }, - { - name: 'invalid_checksum', - type: 'long', - }, - { - name: 'segment_memcap_drop', - type: 'long', - }, - { - name: 'overlap', - type: 'long', - }, - { - name: 'insert_list_fail', - type: 'long', - }, - { - name: 'rst', - type: 'long', - }, - { - name: 'stream_depth_reached', - type: 'long', - }, - { - name: 'reassembly_memuse', - type: 'long', - }, - { - name: 'reassembly_gap', - type: 'long', - }, - { - name: 'overlap_diff_data', - type: 'long', - }, - { - name: 'no_flow', - type: 'long', - }, - ], - }, - { - name: 'decoder', - type: 'group', - fields: [ - { - name: 'avg_pkt_size', - type: 'long', - }, - { - name: 'bytes', - type: 'long', - }, - { - name: 'tcp', - type: 'long', - }, - { - name: 'raw', - type: 'long', - }, - { - name: 'ppp', - type: 'long', - }, - { - name: 'vlan_qinq', - type: 'long', - }, - { - name: 'null', - type: 'long', - }, - { - name: 'ltnull', - type: 'group', - fields: [ - { - name: 'unsupported_type', - type: 'long', - }, - { - name: 'pkt_too_small', - type: 'long', - }, - ], - }, - { - name: 'invalid', - type: 'long', - }, - { - name: 'gre', - type: 'long', - }, - { - name: 'ipv4', - type: 'long', - }, - { - name: 'ipv6', - type: 'long', - }, - { - name: 'pkts', - type: 'long', - }, - { - name: 'ipv6_in_ipv6', - type: 'long', - }, - { - name: 'ipraw', - type: 'group', - fields: [ - { - name: 'invalid_ip_version', - type: 'long', - }, - ], - }, - { - name: 'pppoe', - type: 'long', - }, - { - name: 'udp', - type: 'long', - }, - { - name: 'dce', - type: 'group', - fields: [ - { - name: 'pkt_too_small', - type: 'long', - }, - ], - }, - { - name: 'vlan', - type: 'long', - }, - { - name: 'sctp', - type: 'long', - }, - { - name: 'max_pkt_size', - type: 'long', - }, - { - name: 'teredo', - type: 'long', - }, - { - name: 'mpls', - type: 'long', - }, - { - name: 'sll', - type: 'long', - }, - { - name: 'icmpv6', - type: 'long', - }, - { - name: 'icmpv4', - type: 'long', - }, - { - name: 'erspan', - type: 'long', - }, - { - name: 'ethernet', - type: 'long', - }, - { - name: 'ipv4_in_ipv6', - type: 'long', - }, - { - name: 'ieee8021ah', - type: 'long', - }, - ], - }, - { - name: 'dns', - type: 'group', - fields: [ - { - name: 'memcap_global', - type: 'long', - }, - { - name: 'memcap_state', - type: 'long', - }, - { - name: 'memuse', - type: 'long', - }, - ], - }, - { - name: 'flow_mgr', - type: 'group', - fields: [ - { - name: 'rows_busy', - type: 'long', - }, - { - name: 'flows_timeout', - type: 'long', - }, - { - name: 'flows_notimeout', - type: 'long', - }, - { - name: 'rows_skipped', - type: 'long', - }, - { - name: 'closed_pruned', - type: 'long', - }, - { - name: 'new_pruned', - type: 'long', - }, - { - name: 'flows_removed', - type: 'long', - }, - { - name: 'bypassed_pruned', - type: 'long', - }, - { - name: 'est_pruned', - type: 'long', - }, - { - name: 'flows_timeout_inuse', - type: 'long', - }, - { - name: 'flows_checked', - type: 'long', - }, - { - name: 'rows_maxlen', - type: 'long', - }, - { - name: 'rows_checked', - type: 'long', - }, - { - name: 'rows_empty', - type: 'long', - }, - ], - }, - { - name: 'app_layer', - type: 'group', - fields: [ - { - name: 'flow', - type: 'group', - fields: [ - { - name: 'tls', - type: 'long', - }, - { - name: 'ftp', - type: 'long', - }, - { - name: 'http', - type: 'long', - }, - { - name: 'failed_udp', - type: 'long', - }, - { - name: 'dns_udp', - type: 'long', - }, - { - name: 'dns_tcp', - type: 'long', - }, - { - name: 'smtp', - type: 'long', - }, - { - name: 'failed_tcp', - type: 'long', - }, - { - name: 'msn', - type: 'long', - }, - { - name: 'ssh', - type: 'long', - }, - { - name: 'imap', - type: 'long', - }, - { - name: 'dcerpc_udp', - type: 'long', - }, - { - name: 'dcerpc_tcp', - type: 'long', - }, - { - name: 'smb', - type: 'long', - }, - ], - }, - { - name: 'tx', - type: 'group', - fields: [ - { - name: 'tls', - type: 'long', - }, - { - name: 'ftp', - type: 'long', - }, - { - name: 'http', - type: 'long', - }, - { - name: 'dns_udp', - type: 'long', - }, - { - name: 'dns_tcp', - type: 'long', - }, - { - name: 'smtp', - type: 'long', - }, - { - name: 'ssh', - type: 'long', - }, - { - name: 'dcerpc_udp', - type: 'long', - }, - { - name: 'dcerpc_tcp', - type: 'long', - }, - { - name: 'smb', - type: 'long', - }, - ], - }, - ], - }, - ], - }, - { - name: 'tls', - type: 'group', - fields: [ - { - name: 'notbefore', - type: 'date', - }, - { - name: 'issuerdn', - type: 'keyword', - }, - { - name: 'sni', - type: 'keyword', - }, - { - name: 'version', - type: 'keyword', - }, - { - name: 'session_resumed', - type: 'boolean', - }, - { - name: 'fingerprint', - type: 'keyword', - }, - { - name: 'serial', - type: 'keyword', - }, - { - name: 'notafter', - type: 'date', - }, - { - name: 'subject', - type: 'keyword', - }, - ], - }, - { - name: 'app_proto_ts', - type: 'keyword', - }, - { - name: 'flow', - type: 'group', - fields: [ - { - name: 'bytes_toclient', - type: 'alias', - path: 'destination.bytes', - }, - { - name: 'start', - type: 'alias', - path: 'event.start', - }, - { - name: 'pkts_toclient', - type: 'alias', - path: 'destination.packets', - }, - { - name: 'age', - type: 'long', - }, - { - name: 'state', - type: 'keyword', - }, - { - name: 'bytes_toserver', - type: 'alias', - path: 'source.bytes', - }, - { - name: 'reason', - type: 'keyword', - }, - { - name: 'pkts_toserver', - type: 'alias', - path: 'source.packets', - }, - { - name: 'end', - type: 'date', - }, - { - name: 'alerted', - type: 'boolean', - }, - ], - }, - { - name: 'app_proto', - type: 'alias', - path: 'network.protocol', - }, - { - name: 'tx_id', - type: 'long', - }, - { - name: 'app_proto_tc', - type: 'keyword', - }, - { - name: 'smtp', - type: 'group', - fields: [ - { - name: 'rcpt_to', - type: 'keyword', - }, - { - name: 'mail_from', - type: 'keyword', - }, - { - name: 'helo', - type: 'keyword', - }, - ], - }, - { - name: 'app_proto_expected', - type: 'keyword', - }, - { - name: 'flags', - type: 'group', - fields: [], - }, - ], - }, - ], - }, - ], - }, - { - key: 'zeek', - title: 'Zeek', - description: 'Module for handling logs produced by Zeek/Bro\n', - fields: [ - { - name: 'zeek', - type: 'group', - description: 'Fields from Zeek/Bro logs after normalization\n', - fields: [ - { - name: 'session_id', - type: 'keyword', - description: 'A unique identifier of the session\n', - }, - { - name: 'capture_loss', - type: 'group', - description: 'Fields exported by the Zeek capture_loss log\n', - fields: [ - { - name: 'ts_delta', - type: 'integer', - description: 'The time delay between this measurement and the last.\n', - }, - { - name: 'peer', - type: 'keyword', - description: - 'In the event that there are multiple Bro instances logging to the same host, this distinguishes each peer with its individual name.\n', - }, - { - name: 'gaps', - type: 'integer', - description: 'Number of missed ACKs from the previous measurement interval.\n', - }, - { - name: 'acks', - type: 'integer', - description: 'Total number of ACKs seen in the previous measurement interval.\n', - }, - { - name: 'percent_lost', - type: 'double', - description: "Percentage of ACKs seen where the data being ACKed wasn't seen.\n", - }, - ], - }, - { - name: 'connection', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek Connection log\n', - fields: [ - { - name: 'local_orig', - type: 'boolean', - description: 'Indicates whether the session is originated locally.\n', - }, - { - name: 'local_resp', - type: 'boolean', - description: 'Indicates whether the session is responded locally.\n', - }, - { - name: 'missed_bytes', - type: 'long', - description: 'Missed bytes for the session.\n', - }, - { - name: 'state', - type: 'keyword', - description: 'Code indicating the state of the session.\n', - }, - { - name: 'state_message', - type: 'keyword', - description: 'The state of the session.\n', - }, - { - name: 'icmp', - type: 'group', - fields: [ - { - name: 'type', - type: 'integer', - description: 'ICMP message type.\n', - }, - { - name: 'code', - type: 'integer', - description: 'ICMP message code.\n', - }, - ], - }, - { - name: 'history', - type: 'keyword', - description: 'Flags indicating the history of the session.\n', - }, - { - name: 'vlan', - type: 'integer', - description: 'VLAN identifier.\n', - }, - { - name: 'inner_vlan', - type: 'integer', - description: 'VLAN identifier.\n', - }, - ], - }, - { - name: 'dce_rpc', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek DCE_RPC log\n', - fields: [ - { - name: 'rtt', - type: 'integer', - description: - "Round trip time from the request to the response. If either the request or response wasn't seen, this will be null.\n", - }, - { - name: 'named_pipe', - type: 'keyword', - description: 'Remote pipe name.\n', - }, - { - name: 'endpoint', - type: 'keyword', - description: 'Endpoint name looked up from the uuid.\n', - }, - { - name: 'operation', - type: 'keyword', - description: 'Operation seen in the call.\n', - }, - ], - }, - { - name: 'dhcp', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek DHCP log\n', - fields: [ - { - name: 'domain', - type: 'keyword', - description: 'Domain given by the server in option 15.\n', - }, - { - name: 'duration', - type: 'double', - description: - 'Duration of the DHCP session representing the time from the first\nmessage to the last, in seconds.\n', - }, - { - name: 'hostname', - type: 'keyword', - description: 'Name given by client in Hostname option 12.\n', - }, - { - name: 'client_fqdn', - type: 'keyword', - description: 'FQDN given by client in Client FQDN option 81.\n', - }, - { - name: 'lease_time', - type: 'integer', - description: 'IP address lease interval in seconds.\n', - }, - { - name: 'address', - type: 'group', - description: 'Addresses seen in this DHCP exchange.\n', - fields: [ - { - name: 'assigned', - type: 'ip', - description: 'IP address assigned by the server.\n', - }, - { - name: 'client', - type: 'ip', - description: - 'IP address of the client. If a transaction is only a client sending\nINFORM messages then there is no lease information exchanged so this\nis helpful to know who sent the messages. Getting an address in this\nfield does require that the client sources at least one DHCP message\nusing a non-broadcast address.\n', - }, - { - name: 'mac', - type: 'keyword', - description: "Client's hardware address.\n", - }, - { - name: 'requested', - type: 'ip', - description: 'IP address requested by the client.\n', - }, - { - name: 'server', - type: 'ip', - description: 'IP address of the DHCP server.\n', - }, - ], - }, - { - name: 'msg', - type: 'group', - fields: [ - { - name: 'types', - type: 'keyword', - description: 'List of DHCP message types seen in this exchange.\n', - }, - { - name: 'origin', - type: 'ip', - description: - '(present if policy/protocols/dhcp/msg-orig.bro is loaded)\nThe address that originated each message from the msg.types field.\n', - }, - { - name: 'client', - type: 'keyword', - description: - 'Message typically accompanied with a DHCP_DECLINE so the client can\ntell the server why it rejected an address.\n', - }, - { - name: 'server', - type: 'keyword', - description: - 'Message typically accompanied with a DHCP_NAK to let the client know\nwhy it rejected the request.\n', - }, - ], - }, - { - name: 'software', - type: 'group', - fields: [ - { - name: 'client', - type: 'keyword', - description: - '(present if policy/protocols/dhcp/software.bro is loaded)\nSoftware reported by the client in the vendor_class option.\n', - }, - { - name: 'server', - type: 'keyword', - description: - '(present if policy/protocols/dhcp/software.bro is loaded)\nSoftware reported by the client in the vendor_class option.\n', - }, - ], - }, - { - name: 'id', - type: 'group', - fields: [ - { - name: 'circuit', - type: 'keyword', - description: - '(present if policy/protocols/dhcp/sub-opts.bro is loaded)\nAdded by DHCP relay agents which terminate switched or permanent\ncircuits. It encodes an agent-local identifier of the circuit from\nwhich a DHCP client-to-server packet was received. Typically it\nshould represent a router or switch interface number.\n', - }, - { - name: 'remote_agent', - type: 'keyword', - description: - '(present if policy/protocols/dhcp/sub-opts.bro is loaded)\nA globally unique identifier added by relay agents to identify the\nremote host end of the circuit.\n', - }, - { - name: 'subscriber', - type: 'keyword', - description: - "(present if policy/protocols/dhcp/sub-opts.bro is loaded)\nThe subscriber ID is a value independent of the physical network\nconfiguration so that a customer's DHCP configuration can be given\nto them correctly no matter where they are physically connected.\n", - }, - ], - }, - ], - }, - { - name: 'dnp3', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek SSH log\n', - fields: [ - { - name: 'function', - type: 'group', - fields: [ - { - name: 'request', - type: 'keyword', - description: 'The name of the function message in the request.\n', - }, - { - name: 'reply', - type: 'keyword', - description: 'The name of the function message in the reply.\n', - }, - ], - }, - { - name: 'id', - type: 'integer', - description: "The response's internal indication number.\n", - }, - ], - }, - { - name: 'dns', - type: 'group', - description: 'Fields exported by the Zeek DNS log\n', - fields: [ - { - name: 'trans_id', - type: 'keyword', - description: 'DNS transaction identifier.\n', - }, - { - name: 'rtt', - type: 'double', - description: 'Round trip time for the query and response.\n', - }, - { - name: 'query', - type: 'keyword', - description: 'The domain name that is the subject of the DNS query.\n', - }, - { - name: 'qclass', - type: 'long', - description: 'The QCLASS value specifying the class of the query.\n', - }, - { - name: 'qclass_name', - type: 'keyword', - description: 'A descriptive name for the class of the query.\n', - }, - { - name: 'qtype', - type: 'long', - description: 'A QTYPE value specifying the type of the query.\n', - }, - { - name: 'qtype_name', - type: 'keyword', - description: 'A descriptive name for the type of the query.\n', - }, - { - name: 'rcode', - type: 'long', - description: 'The response code value in DNS response messages.\n', - }, - { - name: 'rcode_name', - type: 'keyword', - description: 'A descriptive name for the response code value.\n', - }, - { - name: 'AA', - type: 'boolean', - description: - 'The Authoritative Answer bit for response messages specifies that the responding\nname server is an authority for the domain name in the question section.\n', - }, - { - name: 'TC', - type: 'boolean', - description: 'The Truncation bit specifies that the message was truncated.\n', - }, - { - name: 'RD', - type: 'boolean', - description: - 'The Recursion Desired bit in a request message indicates that the client\nwants recursive service for this query.\n', - }, - { - name: 'RA', - type: 'boolean', - description: - 'The Recursion Available bit in a response message indicates that the name\nserver supports recursive queries.\n', - }, - { - name: 'answers', - type: 'keyword', - description: 'The set of resource descriptions in the query answer.\n', - }, - { - name: 'TTLs', - type: 'double', - description: - 'The caching intervals of the associated RRs described by the answers field.\n', - }, - { - name: 'rejected', - type: 'boolean', - description: 'Indicates whether the DNS query was rejected by the server.\n', - }, - { - name: 'total_answers', - type: 'integer', - description: 'The total number of resource records in the reply.\n', - }, - { - name: 'total_replies', - type: 'integer', - description: 'The total number of resource records in the reply message.\n', - }, - { - name: 'saw_query', - type: 'boolean', - description: 'Whether the full DNS query has been seen.\n', - }, - { - name: 'saw_reply', - type: 'boolean', - description: 'Whether the full DNS reply has been seen.\n', - }, - ], - }, - { - name: 'dpd', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek DPD log\n', - fields: [ - { - name: 'analyzer', - type: 'keyword', - description: 'The analyzer that generated the violation.\n', - }, - { - name: 'failure_reason', - type: 'keyword', - description: 'The textual reason for the analysis failure.\n', - }, - { - name: 'packet_segment', - type: 'keyword', - description: - '(present if policy/frameworks/dpd/packet-segment-logging.bro is loaded)\nA chunk of the payload that most likely resulted in the protocol violation.\n', - }, - ], - }, - { - name: 'files', - type: 'group', - description: 'Fields exported by the Zeek Files log.\n', - fields: [ - { - name: 'fuid', - type: 'keyword', - description: 'A file unique identifier.\n', - }, - { - name: 'tx_host', - type: 'ip', - description: 'The host that transferred the file.\n', - }, - { - name: 'rx_host', - type: 'ip', - description: 'The host that received the file.\n', - }, - { - name: 'session_ids', - type: 'keyword', - description: 'The sessions that have this file.\n', - }, - { - name: 'source', - type: 'keyword', - description: - 'An identification of the source of the file data. E.g. it may be a network protocol\nover which it was transferred, or a local file path which was read, or some other\ninput source.\n', - }, - { - name: 'depth', - type: 'long', - description: - 'A value to represent the depth of this file in relation to its source. In SMTP, it\nis the depth of the MIME attachment on the message. In HTTP, it is the depth of the\nrequest within the TCP connection.\n', - }, - { - name: 'analyzers', - type: 'keyword', - description: 'A set of analysis types done during the file analysis.\n', - }, - { - name: 'mime_type', - type: 'keyword', - description: 'Mime type of the file.\n', - }, - { - name: 'filename', - type: 'keyword', - description: 'Name of the file if available.\n', - }, - { - name: 'local_orig', - type: 'boolean', - description: - 'If the source of this file is a network connection, this field indicates if the data\noriginated from the local network or not.\n', - }, - { - name: 'is_orig', - type: 'boolean', - description: - 'If the source of this file is a network connection, this field indicates if the file is\nbeing sent by the originator of the connection or the responder.\n', - }, - { - name: 'duration', - type: 'double', - description: - 'The duration the file was analyzed for. Not the duration of the session.\n', - }, - { - name: 'seen_bytes', - type: 'long', - description: 'Number of bytes provided to the file analysis engine for the file.\n', - }, - { - name: 'total_bytes', - type: 'long', - description: 'Total number of bytes that are supposed to comprise the full file.\n', - }, - { - name: 'missing_bytes', - type: 'long', - description: - 'The number of bytes in the file stream that were completely missed during the process\nof analysis.\n', - }, - { - name: 'overflow_bytes', - type: 'long', - description: - "The number of bytes in the file stream that were not delivered to stream file analyzers.\nThis could be overlapping bytes or bytes that couldn't be reassembled.\n", - }, - { - name: 'timedout', - type: 'boolean', - description: 'Whether the file analysis timed out at least once for the file.\n', - }, - { - name: 'parent_fuid', - type: 'keyword', - description: - 'Identifier associated with a container file from which this one was extracted as part of\nthe file analysis.\n', - }, - { - name: 'md5', - type: 'keyword', - description: 'An MD5 digest of the file contents.\n', - }, - { - name: 'sha1', - type: 'keyword', - description: 'A SHA1 digest of the file contents.\n', - }, - { - name: 'sha256', - type: 'keyword', - description: 'A SHA256 digest of the file contents.\n', - }, - { - name: 'extracted', - type: 'keyword', - description: 'Local filename of extracted file.\n', - }, - { - name: 'extracted_cutoff', - type: 'boolean', - description: - 'Indicate whether the file being extracted was cut off hence not extracted completely.\n', - }, - { - name: 'extracted_size', - type: 'long', - description: 'The number of bytes extracted to disk.\n', - }, - { - name: 'entropy', - type: 'double', - description: 'The information density of the contents of the file.\n', - }, - ], - }, - { - name: 'ftp', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek FTP log\n', - fields: [ - { - name: 'user', - type: 'keyword', - description: 'User name for the current FTP session.\n', - }, - { - name: 'password', - type: 'keyword', - description: 'Password for the current FTP session if captured.\n', - }, - { - name: 'command', - type: 'keyword', - description: 'Command given by the client.\n', - }, - { - name: 'arg', - type: 'keyword', - description: 'Argument for the command if one is given.\n', - }, - { - name: 'file', - type: 'group', - fields: [ - { - name: 'size', - type: 'long', - description: 'Size of the file if the command indicates a file transfer.\n', - }, - { - name: 'mime_type', - type: 'keyword', - description: 'Sniffed mime type of file.\n', - }, - { - name: 'fuid', - type: 'keyword', - description: - '(present if base/protocols/ftp/files.bro is loaded)\nFile unique ID.\n', - }, - ], - }, - { - name: 'reply', - type: 'group', - fields: [ - { - name: 'code', - type: 'integer', - description: 'Reply code from the server in response to the command.\n', - }, - { - name: 'msg', - type: 'keyword', - description: 'Reply message from the server in response to the command.\n', - }, - ], - }, - { - name: 'data_channel', - type: 'group', - description: 'Expected FTP data channel.\n', - fields: [ - { - name: 'passive', - type: 'boolean', - description: 'Whether PASV mode is toggled for control channel.\n', - }, - { - name: 'originating_host', - type: 'ip', - description: 'The host that will be initiating the data connection.\n', - }, - { - name: 'response_host', - type: 'ip', - description: 'The host that will be accepting the data connection.\n', - }, - { - name: 'response_port', - type: 'integer', - description: - 'The port at which the acceptor is listening for the data connection.\n', - }, - ], - }, - { - name: 'cwd', - type: 'keyword', - description: - "Current working directory that this session is in. By making the default value '.', we can indicate that unless something more concrete is discovered that the existing but unknown directory is ok to use.\n", - }, - { - name: 'cmdarg', - type: 'group', - description: 'Command that is currently waiting for a response.\n', - fields: [ - { - name: 'cmd', - type: 'keyword', - description: 'Command.\n', - }, - { - name: 'arg', - type: 'keyword', - description: 'Argument for the command if one was given.\n', - }, - { - name: 'seq', - type: 'integer', - description: 'Counter to track how many commands have been executed.\n', - }, - ], - }, - { - name: 'pending_commands', - type: 'integer', - description: - 'Queue for commands that have been sent but not yet responded to are tracked here.\n', - }, - { - name: 'passive', - type: 'boolean', - description: 'Indicates if the session is in active or passive mode.\n', - }, - { - name: 'capture_password', - type: 'boolean', - description: 'Determines if the password will be captured for this request.\n', - }, - { - name: 'last_auth_requested', - type: 'keyword', - description: - 'present if base/protocols/ftp/gridftp.bro is loaded.\nLast authentication/security mechanism that was used.\n', - }, - ], - }, - { - name: 'http', - type: 'group', - description: 'Fields exported by the Zeek HTTP log\n', - fields: [ - { - name: 'trans_depth', - type: 'integer', - description: - 'Represents the pipelined depth into the connection of this request/response transaction.\n', - }, - { - name: 'status_msg', - type: 'keyword', - description: 'Status message returned by the server.\n', - }, - { - name: 'info_code', - type: 'integer', - description: 'Last seen 1xx informational reply code returned by the server.\n', - }, - { - name: 'info_msg', - type: 'keyword', - description: 'Last seen 1xx informational reply message returned by the server.\n', - }, - { - name: 'tags', - type: 'keyword', - description: - 'A set of indicators of various attributes discovered and related to a particular\nrequest/response pair.\n', - }, - { - name: 'password', - type: 'keyword', - description: 'Password if basic-auth is performed for the request.\n', - }, - { - name: 'captured_password', - type: 'boolean', - description: 'Determines if the password will be captured for this request.\n', - }, - { - name: 'proxied', - type: 'keyword', - description: - 'All of the headers that may indicate if the HTTP request was proxied.\n', - }, - { - name: 'range_request', - type: 'boolean', - description: - 'Indicates if this request can assume 206 partial content in response.\n', - }, - { - name: 'client_header_names', - type: 'keyword', - description: - 'The vector of HTTP header names sent by the client. No header values\nare included here, just the header names.\n', - }, - { - name: 'server_header_names', - type: 'keyword', - description: - 'The vector of HTTP header names sent by the server. No header values\nare included here, just the header names.\n', - }, - { - name: 'orig_fuids', - type: 'keyword', - description: 'An ordered vector of file unique IDs from the originator.\n', - }, - { - name: 'orig_mime_types', - type: 'keyword', - description: 'An ordered vector of mime types from the originator.\n', - }, - { - name: 'orig_filenames', - type: 'keyword', - description: 'An ordered vector of filenames from the originator.\n', - }, - { - name: 'resp_fuids', - type: 'keyword', - description: 'An ordered vector of file unique IDs from the responder.\n', - }, - { - name: 'resp_mime_types', - type: 'keyword', - description: 'An ordered vector of mime types from the responder.\n', - }, - { - name: 'resp_filenames', - type: 'keyword', - description: 'An ordered vector of filenames from the responder.\n', - }, - { - name: 'orig_mime_depth', - type: 'integer', - description: 'Current number of MIME entities in the HTTP request message body.\n', - }, - { - name: 'resp_mime_depth', - type: 'integer', - description: 'Current number of MIME entities in the HTTP response message body.\n', - }, - ], - }, - { - name: 'intel', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek Intel log.\n', - fields: [ - { - name: 'seen', - type: 'group', - fields: [ - { - name: 'indicator', - type: 'keyword', - description: 'The intelligence indicator.\n', - }, - { - name: 'indicator_type', - type: 'keyword', - description: 'The type of data the indicator represents.\n', - }, - { - name: 'host', - type: 'keyword', - description: - 'If the indicator type was Intel::ADDR, then this field will be present.\n', - }, - { - name: 'conn', - type: 'keyword', - description: - 'If the data was discovered within a connection, the connection record should go here to give context to the data.\n', - }, - { - name: 'where', - type: 'keyword', - description: 'Where the data was discovered.\n', - }, - { - name: 'node', - type: 'keyword', - description: 'The name of the node where the match was discovered.\n', - }, - { - name: 'uid', - type: 'keyword', - description: - 'If the data was discovered within a connection, the connection uid should go here to give context to the data. If the conn field is provided, this will be automatically filled out.\n', - }, - { - name: 'f', - type: 'object', - description: - 'If the data was discovered within a file, the file record should go here to provide context to the data.\n', - }, - { - name: 'fuid', - type: 'keyword', - description: - 'If the data was discovered within a file, the file uid should go here to provide context to the data. If the file record f is provided, this will be automatically filled out.\n', - }, - ], - }, - { - name: 'matched', - type: 'keyword', - description: - 'Event to represent a match in the intelligence data from data that was seen.\n', - }, - { - name: 'sources', - type: 'keyword', - description: 'Sources which supplied data for this match.\n', - }, - { - name: 'fuid', - type: 'keyword', - description: - 'If a file was associated with this intelligence hit, this is the uid for the file.\n', - }, - { - name: 'file_mime_type', - type: 'keyword', - description: - 'A mime type if the intelligence hit is related to a file. If the $f field is provided this will be automatically filled out.\n', - }, - { - name: 'file_desc', - type: 'keyword', - description: - 'Frequently files can be described to give a bit more context. If the $f field is provided this field will be automatically filled out.\n', - }, - ], - }, - { - name: 'irc', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek IRC log\n', - fields: [ - { - name: 'nick', - type: 'keyword', - description: 'Nickname given for the connection.\n', - }, - { - name: 'user', - type: 'keyword', - description: 'Username given for the connection.\n', - }, - { - name: 'command', - type: 'keyword', - description: 'Command given by the client.\n', - }, - { - name: 'value', - type: 'keyword', - description: 'Value for the command given by the client.\n', - }, - { - name: 'addl', - type: 'keyword', - description: 'Any additional data for the command.\n', - }, - { - name: 'dcc', - type: 'group', - fields: [ - { - name: 'file', - type: 'group', - fields: [ - { - name: 'name', - type: 'keyword', - description: - 'Present if base/protocols/irc/dcc-send.bro is loaded.\nDCC filename requested.\n', - }, - { - name: 'size', - type: 'long', - description: - 'Present if base/protocols/irc/dcc-send.bro is loaded.\nSize of the DCC transfer as indicated by the sender.\n', - }, - ], - }, - { - name: 'mime_type', - type: 'keyword', - description: - 'present if base/protocols/irc/dcc-send.bro is loaded.\nSniffed mime type of the file.\n', - }, - ], - }, - { - name: 'fuid', - type: 'keyword', - description: - 'present if base/protocols/irc/files.bro is loaded.\nFile unique ID.\n', - }, - ], - }, - { - name: 'kerberos', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek Kerberos log\n', - fields: [ - { - name: 'request_type', - type: 'keyword', - description: - 'Request type - Authentication Service (AS) or Ticket Granting Service (TGS).\n', - }, - { - name: 'client', - type: 'keyword', - description: 'Client name.\n', - }, - { - name: 'service', - type: 'keyword', - description: 'Service name.\n', - }, - { - name: 'success', - type: 'boolean', - description: 'Request result.\n', - }, - { - name: 'error', - type: 'group', - fields: [ - { - name: 'code', - type: 'integer', - description: 'Error code.\n', - }, - { - name: 'msg', - type: 'keyword', - description: 'Error message.\n', - }, - ], - }, - { - name: 'valid', - type: 'group', - fields: [ - { - name: 'from', - type: 'date', - description: 'Ticket valid from.\n', - }, - { - name: 'until', - type: 'date', - description: 'Ticket valid until.\n', - }, - { - name: 'days', - type: 'integer', - description: 'Number of days the ticket is valid for.\n', - }, - ], - }, - { - name: 'cipher', - type: 'keyword', - description: 'Ticket encryption type.\n', - }, - { - name: 'forwardable', - type: 'boolean', - description: 'Forwardable ticket requested.\n', - }, - { - name: 'renewable', - type: 'boolean', - description: 'Renewable ticket requested.\n', - }, - { - name: 'ticket', - type: 'group', - fields: [ - { - name: 'auth', - type: 'keyword', - description: 'Hash of ticket used to authorize request/transaction.\n', - }, - { - name: 'new', - type: 'keyword', - description: 'Hash of ticket returned by the KDC.\n', - }, - ], - }, - { - name: 'cert', - type: 'group', - fields: [ - { - name: 'client', - type: 'group', - fields: [ - { - name: 'value', - type: 'keyword', - description: 'Client certificate.\n', - }, - { - name: 'fuid', - type: 'keyword', - description: 'File unique ID of client cert.\n', - }, - { - name: 'subject', - type: 'keyword', - description: 'Subject of client certificate.\n', - }, - ], - }, - { - name: 'server', - type: 'group', - fields: [ - { - name: 'value', - type: 'keyword', - description: 'Server certificate.\n', - }, - { - name: 'fuid', - type: 'keyword', - description: 'File unique ID of server certificate.\n', - }, - { - name: 'subject', - type: 'keyword', - description: 'Subject of server certificate.\n', - }, - ], - }, - ], - }, - ], - }, - { - name: 'modbus', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek modbus log.\n', - fields: [ - { - name: 'function', - type: 'keyword', - description: 'The name of the function message that was sent.\n', - }, - { - name: 'exception', - type: 'keyword', - description: 'The exception if the response was a failure.\n', - }, - { - name: 'track_address', - type: 'integer', - description: - 'Present if policy/protocols/modbus/track-memmap.bro is loaded.\nModbus track address.\n', - }, - ], - }, - { - name: 'mysql', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek MySQL log.\n', - fields: [ - { - name: 'cmd', - type: 'keyword', - description: 'The command that was issued.\n', - }, - { - name: 'arg', - type: 'keyword', - description: 'The argument issued to the command.\n', - }, - { - name: 'success', - type: 'boolean', - description: 'Whether the command succeeded.\n', - }, - { - name: 'rows', - type: 'integer', - description: 'The number of affected rows, if any.\n', - }, - { - name: 'response', - type: 'keyword', - description: 'Server message, if any.\n', - }, - ], - }, - { - name: 'notice', - type: 'group', - description: 'Fields exported by the Zeek Notice log.\n', - fields: [ - { - name: 'connection_id', - type: 'keyword', - description: 'Identifier of the related connection session.\n', - }, - { - name: 'icmp_id', - type: 'keyword', - description: 'Identifier of the related ICMP session.\n', - }, - { - name: 'file.id', - type: 'keyword', - description: - 'An identifier associated with a single file that is related to this notice.\n', - }, - { - name: 'file.parent_id', - type: 'keyword', - description: - 'Identifier associated with a container file from which this one was extracted.\n', - }, - { - name: 'file.source', - type: 'keyword', - description: - 'An identification of the source of the file data. E.g. it may be a network protocol\nover which it was transferred, or a local file path which was read, or some other\ninput source.\n', - }, - { - name: 'file.mime_type', - type: 'keyword', - description: 'A mime type if the notice is related to a file.\n', - }, - { - name: 'file.is_orig', - type: 'boolean', - description: - 'If the source of this file is a network connection, this field indicates if the file is\nbeing sent by the originator of the connection or the responder.\n', - }, - { - name: 'file.seen_bytes', - type: 'long', - description: 'Number of bytes provided to the file analysis engine for the file.\n', - }, - { - name: 'ffile.total_bytes', - type: 'long', - description: 'Total number of bytes that are supposed to comprise the full file.\n', - }, - { - name: 'file.missing_bytes', - type: 'long', - description: - 'The number of bytes in the file stream that were completely missed during the process\nof analysis.\n', - }, - { - name: 'file.overflow_bytes', - type: 'long', - description: - "The number of bytes in the file stream that were not delivered to stream file analyzers.\nThis could be overlapping bytes or bytes that couldn't be reassembled.\n", - }, - { - name: 'fuid', - type: 'keyword', - description: 'A file unique ID if this notice is related to a file.\n', - }, - { - name: 'note', - type: 'keyword', - description: 'The type of the notice.\n', - }, - { - name: 'msg', - type: 'keyword', - description: 'The human readable message for the notice.\n', - }, - { - name: 'sub', - type: 'keyword', - description: 'The human readable sub-message.\n', - }, - { - name: 'n', - type: 'long', - description: 'Associated count, or a status code.\n', - }, - { - name: 'peer_name', - type: 'keyword', - description: 'Name of remote peer that raised this notice.\n', - }, - { - name: 'peer_descr', - type: 'text', - description: 'Textual description for the peer that raised this notice.\n', - }, - { - name: 'actions', - type: 'keyword', - description: 'The actions which have been applied to this notice.\n', - }, - { - name: 'email_body_sections', - type: 'text', - description: - 'By adding chunks of text into this element, other scripts can expand on notices\nthat are being emailed.\n', - }, - { - name: 'email_delay_tokens', - type: 'keyword', - description: - 'Adding a string token to this set will cause the built-in emailing functionality\nto delay sending the email either the token has been removed or the email\nhas been delayed for the specified time duration.\n', - }, - { - name: 'identifier', - type: 'keyword', - description: - 'This field is provided when a notice is generated for the purpose of deduplicating notices.\n', - }, - { - name: 'suppress_for', - type: 'double', - description: - 'This field indicates the length of time that this unique notice should be suppressed.\n', - }, - { - name: 'dropped', - type: 'boolean', - description: - 'Indicate if the source IP address was dropped and denied network access.\n', - }, - ], - }, - { - name: 'ntlm', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek NTLM log.\n', - fields: [ - { - name: 'domain', - type: 'keyword', - description: 'Domain name given by the client.\n', - }, - { - name: 'hostname', - type: 'keyword', - description: 'Hostname given by the client.\n', - }, - { - name: 'success', - type: 'boolean', - description: 'Indicate whether or not the authentication was successful.\n', - }, - { - name: 'username', - type: 'keyword', - description: 'Username given by the client.\n', - }, - { - name: 'server', - type: 'group', - fields: [ - { - name: 'name', - type: 'group', - fields: [ - { - name: 'dns', - type: 'keyword', - description: 'DNS name given by the server in a CHALLENGE.\n', - }, - { - name: 'netbios', - type: 'keyword', - description: 'NetBIOS name given by the server in a CHALLENGE.\n', - }, - { - name: 'tree', - type: 'keyword', - description: 'Tree name given by the server in a CHALLENGE.\n', - }, - ], - }, - ], - }, - ], - }, - { - name: 'ocsp', - type: 'group', - default_field: false, - description: - 'Fields exported by the Zeek OCSP log\nOnline Certificate Status Protocol (OCSP). Only created if policy script is loaded.\n', - fields: [ - { - name: 'file_id', - type: 'keyword', - description: 'File id of the OCSP reply.\n', - }, - { - name: 'hash', - type: 'group', - fields: [ - { - name: 'algorithm', - type: 'keyword', - description: - 'Hash algorithm used to generate issuerNameHash and issuerKeyHash.\n', - }, - { - name: 'issuer', - type: 'group', - fields: [ - { - name: 'name', - type: 'keyword', - description: "Hash of the issuer's distingueshed name.\n", - }, - { - name: 'key', - type: 'keyword', - description: "Hash of the issuer's public key.\n", - }, - ], - }, - ], - }, - { - name: 'serial_number', - type: 'keyword', - description: 'Serial number of the affected certificate.\n', - }, - { - name: 'status', - type: 'keyword', - description: 'Status of the affected certificate.\n', - }, - { - name: 'revoke', - type: 'group', - fields: [ - { - name: 'time', - type: 'date', - description: 'Time at which the certificate was revoked.\n', - }, - { - name: 'reason', - type: 'keyword', - description: 'Reason for which the certificate was revoked.\n', - }, - ], - }, - { - name: 'update', - type: 'group', - fields: [ - { - name: 'this', - type: 'date', - description: - 'The time at which the status being shows is known to have been correct.\n', - }, - { - name: 'next', - type: 'date', - description: - 'The latest time at which new information about the status of the certificate will be available.\n', - }, - ], - }, - ], - }, - { - name: 'pe', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek pe log.\n', - fields: [ - { - name: 'client', - type: 'keyword', - description: "The client's version string.\n", - }, - { - name: 'id', - type: 'keyword', - description: 'File id of this portable executable file.\n', - }, - { - name: 'machine', - type: 'keyword', - description: 'The target machine that the file was compiled for.\n', - }, - { - name: 'compile_time', - type: 'date', - description: 'The time that the file was created at.\n', - }, - { - name: 'os', - type: 'keyword', - description: 'The required operating system.\n', - }, - { - name: 'subsystem', - type: 'keyword', - description: 'The subsystem that is required to run this file.\n', - }, - { - name: 'is_exe', - type: 'boolean', - description: 'Is the file an executable, or just an object file?\n', - }, - { - name: 'is_64bit', - type: 'boolean', - description: 'Is the file a 64-bit executable?\n', - }, - { - name: 'uses_aslr', - type: 'boolean', - description: 'Does the file support Address Space Layout Randomization?\n', - }, - { - name: 'uses_dep', - type: 'boolean', - description: 'Does the file support Data Execution Prevention?\n', - }, - { - name: 'uses_code_integrity', - type: 'boolean', - description: 'Does the file enforce code integrity checks?\n', - }, - { - name: 'uses_seh', - type: 'boolean', - description: 'Does the file use structured exception handing?\n', - }, - { - name: 'has_import_table', - type: 'boolean', - description: 'Does the file have an import table?\n', - }, - { - name: 'has_export_table', - type: 'boolean', - description: 'Does the file have an export table?\n', - }, - { - name: 'has_cert_table', - type: 'boolean', - description: 'Does the file have an attribute certificate table?\n', - }, - { - name: 'has_debug_data', - type: 'boolean', - description: 'Does the file have a debug table?\n', - }, - { - name: 'section_names', - type: 'keyword', - description: 'The names of the sections, in order.\n', - }, - ], - }, - { - name: 'radius', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek Radius log.\n', - fields: [ - { - name: 'username', - type: 'keyword', - description: 'The username, if present.\n', - }, - { - name: 'mac', - type: 'keyword', - description: 'MAC address, if present.\n', - }, - { - name: 'framed_addr', - type: 'ip', - description: - 'The address given to the network access server, if present. This is only a hint from the RADIUS server and the network access server is not required to honor the address.\n', - }, - { - name: 'remote_ip', - type: 'ip', - description: - 'Remote IP address, if present. This is collected from the Tunnel-Client-Endpoint attribute.\n', - }, - { - name: 'connect_info', - type: 'keyword', - description: 'Connect info, if present.\n', - }, - { - name: 'reply_msg', - type: 'keyword', - description: - 'Reply message from the server challenge. This is frequently shown to the user authenticating.\n', - }, - { - name: 'result', - type: 'keyword', - description: 'Successful or failed authentication.\n', - }, - { - name: 'ttl', - type: 'integer', - description: - 'The duration between the first request and either the "Access-Accept" message or an error. If the field is empty, it means that either the request or response was not seen.\n', - }, - { - name: 'logged', - type: 'boolean', - description: 'Whether this has already been logged and can be ignored.\n', - }, - ], - }, - { - name: 'rdp', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek RDP log.\n', - fields: [ - { - name: 'cookie', - type: 'keyword', - description: - 'Cookie value used by the client machine. This is typically a username.\n', - }, - { - name: 'result', - type: 'keyword', - description: - "Status result for the connection. It's a mix between RDP negotation failure messages and GCC server create response messages.\n", - }, - { - name: 'security_protocol', - type: 'keyword', - description: 'Security protocol chosen by the server.\n', - }, - { - name: 'keyboard_layout', - type: 'keyword', - description: 'Keyboard layout (language) of the client machine.\n', - }, - { - name: 'client', - type: 'group', - fields: [ - { - name: 'build', - type: 'keyword', - description: 'RDP client version used by the client machine.\n', - }, - { - name: 'client_name', - type: 'keyword', - description: 'Name of the client machine.\n', - }, - { - name: 'product_id', - type: 'keyword', - description: 'Product ID of the client machine.\n', - }, - ], - }, - { - name: 'desktop', - type: 'group', - fields: [ - { - name: 'width', - type: 'integer', - description: 'Desktop width of the client machine.\n', - }, - { - name: 'height', - type: 'integer', - description: 'Desktop height of the client machine.\n', - }, - { - name: 'color_depth', - type: 'keyword', - description: - 'The color depth requested by the client in the high_color_depth field.\n', - }, - ], - }, - { - name: 'cert', - type: 'group', - fields: [ - { - name: 'type', - type: 'keyword', - description: - 'If the connection is being encrypted with native RDP encryption, this is the type of cert being used.\n', - }, - { - name: 'count', - type: 'integer', - description: - 'The number of certs seen. X.509 can transfer an entire certificate chain.\n', - }, - { - name: 'permanent', - type: 'boolean', - description: - 'Indicates if the provided certificate or certificate chain is permanent or temporary.\n', - }, - ], - }, - { - name: 'encryption', - type: 'group', - fields: [ - { - name: 'level', - type: 'keyword', - description: 'Encryption level of the connection.\n', - }, - { - name: 'method', - type: 'keyword', - description: 'Encryption method of the connection.\n', - }, - ], - }, - { - name: 'done', - type: 'boolean', - description: 'Track status of logging RDP connections.\n', - }, - { - name: 'ssl', - type: 'boolean', - description: - '(present if policy/protocols/rdp/indicate_ssl.bro is loaded)\nFlag the connection if it was seen over SSL.\n', - }, - ], - }, - { - name: 'rfb', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek RFB log.\n', - fields: [ - { - name: 'version', - type: 'group', - fields: [ - { - name: 'client', - type: 'group', - fields: [ - { - name: 'major', - type: 'keyword', - description: 'Major version of the client.\n', - }, - { - name: 'minor', - type: 'keyword', - description: 'Minor version of the client.\n', - }, - ], - }, - { - name: 'server', - type: 'group', - fields: [ - { - name: 'major', - type: 'keyword', - description: 'Major version of the server.\n', - }, - { - name: 'minor', - type: 'keyword', - description: 'Minor version of the server.\n', - }, - ], - }, - ], - }, - { - name: 'auth', - type: 'group', - fields: [ - { - name: 'success', - type: 'boolean', - description: 'Whether or not authentication was successful.\n', - }, - { - name: 'method', - type: 'keyword', - description: 'Identifier of authentication method used.\n', - }, - ], - }, - { - name: 'share_flag', - type: 'boolean', - description: 'Whether the client has an exclusive or a shared session.\n', - }, - { - name: 'desktop_name', - type: 'keyword', - description: 'Name of the screen that is being shared.\n', - }, - { - name: 'width', - type: 'integer', - description: 'Width of the screen that is being shared.\n', - }, - { - name: 'height', - type: 'integer', - description: 'Height of the screen that is being shared.\n', - }, - ], - }, - { - name: 'sip', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek SIP log.\n', - fields: [ - { - name: 'transaction_depth', - type: 'integer', - description: - 'Represents the pipelined depth into the connection of this request/response transaction.\n', - }, - { - name: 'sequence', - type: 'group', - fields: [ - { - name: 'method', - type: 'keyword', - description: 'Verb used in the SIP request (INVITE, REGISTER etc.).\n', - }, - { - name: 'number', - type: 'keyword', - description: 'Contents of the CSeq: header from the client.\n', - }, - ], - }, - { - name: 'uri', - type: 'keyword', - description: 'URI used in the request.\n', - }, - { - name: 'date', - type: 'keyword', - description: 'Contents of the Date: header from the client.\n', - }, - { - name: 'request', - type: 'group', - fields: [ - { - name: 'from', - type: 'keyword', - description: - "Contents of the request From: header Note: The tag= value that's usually appended to the sender is stripped off and not logged.\n", - }, - { - name: 'to', - type: 'keyword', - description: 'Contents of the To: header.\n', - }, - { - name: 'path', - type: 'keyword', - description: - 'The client message transmission path, as extracted from the headers.\n', - }, - { - name: 'body_length', - type: 'long', - description: 'Contents of the Content-Length: header from the client.\n', - }, - ], - }, - { - name: 'response', - type: 'group', - fields: [ - { - name: 'from', - type: 'keyword', - description: - "Contents of the response From: header Note: The tag= value that's usually appended to the sender is stripped off and not logged.\n", - }, - { - name: 'to', - type: 'keyword', - description: 'Contents of the response To: header.\n', - }, - { - name: 'path', - type: 'keyword', - description: - 'The server message transmission path, as extracted from the headers.\n', - }, - { - name: 'body_length', - type: 'long', - description: 'Contents of the Content-Length: header from the server.\n', - }, - ], - }, - { - name: 'reply_to', - type: 'keyword', - description: 'Contents of the Reply-To: header.\n', - }, - { - name: 'call_id', - type: 'keyword', - description: 'Contents of the Call-ID: header from the client.\n', - }, - { - name: 'subject', - type: 'keyword', - description: 'Contents of the Subject: header from the client.\n', - }, - { - name: 'user_agent', - type: 'keyword', - description: 'Contents of the User-Agent: header from the client.\n', - }, - { - name: 'status', - type: 'group', - fields: [ - { - name: 'code', - type: 'integer', - description: 'Status code returned by the server.\n', - }, - { - name: 'msg', - type: 'keyword', - description: 'Status message returned by the server.\n', - }, - ], - }, - { - name: 'warning', - type: 'keyword', - description: 'Contents of the Warning: header.\n', - }, - { - name: 'content_type', - type: 'keyword', - description: 'Contents of the Content-Type: header from the server.\n', - }, - ], - }, - { - name: 'smb_cmd', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek smb_cmd log.\n', - fields: [ - { - name: 'command', - type: 'keyword', - description: 'The command sent by the client.\n', - }, - { - name: 'sub_command', - type: 'keyword', - description: 'The subcommand sent by the client, if present.\n', - }, - { - name: 'argument', - type: 'keyword', - description: 'Command argument sent by the client, if any.\n', - }, - { - name: 'status', - type: 'keyword', - description: "Server reply to the client's command.\n", - }, - { - name: 'rtt', - type: 'double', - description: 'Round trip time from the request to the response.\n', - }, - { - name: 'version', - type: 'keyword', - description: 'Version of SMB for the command.\n', - }, - { - name: 'username', - type: 'keyword', - description: 'Authenticated username, if available.\n', - }, - { - name: 'tree', - type: 'keyword', - description: - 'If this is related to a tree, this is the tree that was used for the current command.\n', - }, - { - name: 'tree_service', - type: 'keyword', - description: 'The type of tree (disk share, printer share, named pipe, etc.).\n', - }, - { - name: 'file', - type: 'group', - description: 'If the command referenced a file, store it here.\n', - fields: [ - { - name: 'name', - type: 'keyword', - description: 'Filename if one was seen.\n', - }, - { - name: 'action', - type: 'keyword', - description: 'Action this log record represents.\n', - }, - { - name: 'uid', - type: 'keyword', - description: 'UID of the referenced file.\n', - }, - { - name: 'host', - type: 'group', - fields: [ - { - name: 'tx', - type: 'ip', - description: 'Address of the transmitting host.\n', - }, - { - name: 'rx', - type: 'ip', - description: 'Address of the receiving host.\n', - }, - ], - }, - ], - }, - { - name: 'smb1_offered_dialects', - type: 'keyword', - description: - 'Present if base/protocols/smb/smb1-main.bro is loaded.\nDialects offered by the client.\n', - }, - { - name: 'smb2_offered_dialects', - type: 'integer', - description: - 'Present if base/protocols/smb/smb2-main.bro is loaded.\nDialects offered by the client.\n', - }, - ], - }, - { - name: 'smb_files', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek SMB Files log.\n', - fields: [ - { - name: 'action', - type: 'keyword', - description: 'Action this log record represents.\n', - }, - { - name: 'fid', - type: 'integer', - description: 'ID referencing this file.\n', - }, - { - name: 'name', - type: 'keyword', - description: 'Filename if one was seen.\n', - }, - { - name: 'path', - type: 'keyword', - description: 'Path pulled from the tree this file was transferred to or from.\n', - }, - { - name: 'previous_name', - type: 'keyword', - description: - "If the rename action was seen, this will be the file's previous name.\n", - }, - { - name: 'size', - type: 'long', - description: 'Byte size of the file.\n', - }, - { - name: 'times', - type: 'group', - description: 'Timestamps of the file.\n', - fields: [ - { - name: 'accessed', - type: 'date', - description: "The file's access time.\n", - }, - { - name: 'changed', - type: 'date', - description: "The file's change time.\n", - }, - { - name: 'created', - type: 'date', - description: "The file's create time.\n", - }, - { - name: 'modified', - type: 'date', - description: "The file's modify time.\n", - }, - ], - }, - { - name: 'uuid', - type: 'keyword', - description: 'UUID referencing this file if DCE/RPC.\n', - }, - ], - }, - { - name: 'smb_mapping', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek SMB_Mapping log.\n', - fields: [ - { - name: 'path', - type: 'keyword', - description: 'Name of the tree path.\n', - }, - { - name: 'service', - type: 'keyword', - description: - 'The type of resource of the tree (disk share, printer share, named pipe, etc.).\n', - }, - { - name: 'native_file_system', - type: 'keyword', - description: 'File system of the tree.\n', - }, - { - name: 'share_type', - type: 'keyword', - description: - 'If this is SMB2, a share type will be included. For SMB1, the type of share\nwill be deduced and included as well.\n', - }, - ], - }, - { - name: 'smtp', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek SMTP log.\n', - fields: [ - { - name: 'transaction_depth', - type: 'integer', - description: - 'A count to represent the depth of this message transaction in a single connection where multiple messages were transferred.\n', - }, - { - name: 'helo', - type: 'keyword', - description: 'Contents of the Helo header.\n', - }, - { - name: 'mail_from', - type: 'keyword', - description: 'Email addresses found in the MAIL FROM header.\n', - }, - { - name: 'rcpt_to', - type: 'keyword', - description: 'Email addresses found in the RCPT TO header.\n', - }, - { - name: 'date', - type: 'date', - description: 'Contents of the Date header.\n', - }, - { - name: 'from', - type: 'keyword', - description: 'Contents of the From header.\n', - }, - { - name: 'to', - type: 'keyword', - description: 'Contents of the To header.\n', - }, - { - name: 'cc', - type: 'keyword', - description: 'Contents of the CC header.\n', - }, - { - name: 'reply_to', - type: 'keyword', - description: 'Contents of the ReplyTo header.\n', - }, - { - name: 'msg_id', - type: 'keyword', - description: 'Contents of the MsgID header.\n', - }, - { - name: 'in_reply_to', - type: 'keyword', - description: 'Contents of the In-Reply-To header.\n', - }, - { - name: 'subject', - type: 'keyword', - description: 'Contents of the Subject header.\n', - }, - { - name: 'x_originating_ip', - type: 'keyword', - description: 'Contents of the X-Originating-IP header.\n', - }, - { - name: 'first_received', - type: 'keyword', - description: 'Contents of the first Received header.\n', - }, - { - name: 'second_received', - type: 'keyword', - description: 'Contents of the second Received header.\n', - }, - { - name: 'last_reply', - type: 'keyword', - description: 'The last message that the server sent to the client.\n', - }, - { - name: 'path', - type: 'ip', - description: 'The message transmission path, as extracted from the headers.\n', - }, - { - name: 'user_agent', - type: 'keyword', - description: 'Value of the User-Agent header from the client.\n', - }, - { - name: 'tls', - type: 'boolean', - description: 'Indicates that the connection has switched to using TLS.\n', - }, - { - name: 'process_received_from', - type: 'boolean', - description: - 'Indicates if the "Received: from" headers should still be processed.\n', - }, - { - name: 'has_client_activity', - type: 'boolean', - description: 'Indicates if client activity has been seen, but not yet logged.\n', - }, - { - name: 'fuids', - type: 'keyword', - description: - '(present if base/protocols/smtp/files.bro is loaded)\nAn ordered vector of file unique IDs seen attached to the message.\n', - }, - { - name: 'is_webmail', - type: 'boolean', - description: 'Indicates if the message was sent through a webmail interface.\n', - }, - ], - }, - { - name: 'snmp', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek SNMP log.\n', - fields: [ - { - name: 'duration', - type: 'double', - description: - 'The amount of time between the first packet beloning to the SNMP session and the latest one seen.\n', - }, - { - name: 'version', - type: 'keyword', - description: 'The version of SNMP being used.\n', - }, - { - name: 'community', - type: 'keyword', - description: - "The community string of the first SNMP packet associated with the session. This is used as part of SNMP's (v1 and v2c) administrative/security framework. See RFC 1157 or RFC 1901.\n", - }, - { - name: 'get', - type: 'group', - fields: [ - { - name: 'requests', - type: 'integer', - description: - 'The number of variable bindings in GetRequest/GetNextRequest PDUs seen for the session.\n', - }, - { - name: 'bulk_requests', - type: 'integer', - description: - 'The number of variable bindings in GetBulkRequest PDUs seen for the session.\n', - }, - { - name: 'responses', - type: 'integer', - description: - 'The number of variable bindings in GetResponse/Response PDUs seen for the session.\n', - }, - ], - }, - { - name: 'set', - type: 'group', - fields: [ - { - name: 'requests', - type: 'integer', - description: - 'The number of variable bindings in SetRequest PDUs seen for the session.\n', - }, - ], - }, - { - name: 'display_string', - type: 'keyword', - description: 'A system description of the SNMP responder endpoint.\n', - }, - { - name: 'up_since', - type: 'date', - description: - "The time at which the SNMP responder endpoint claims it's been up since.\n", - }, - ], - }, - { - name: 'socks', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek SOCKS log.\n', - fields: [ - { - name: 'version', - type: 'integer', - description: 'Protocol version of SOCKS.\n', - }, - { - name: 'user', - type: 'keyword', - description: 'Username used to request a login to the proxy.\n', - }, - { - name: 'password', - type: 'keyword', - description: 'Password used to request a login to the proxy.\n', - }, - { - name: 'status', - type: 'keyword', - description: 'Server status for the attempt at using the proxy.\n', - }, - { - name: 'request', - type: 'group', - fields: [ - { - name: 'host', - type: 'keyword', - description: - 'Client requested SOCKS address. Could be an address, a name or both.\n', - }, - { - name: 'port', - type: 'integer', - description: 'Client requested port.\n', - }, - ], - }, - { - name: 'bound', - type: 'group', - fields: [ - { - name: 'host', - type: 'keyword', - description: 'Server bound address. Could be an address, a name or both.\n', - }, - { - name: 'port', - type: 'integer', - description: 'Server bound port.\n', - }, - ], - }, - { - name: 'capture_password', - type: 'boolean', - description: 'Determines if the password will be captured for this request.\n', - }, - ], - }, - { - name: 'ssh', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek SSH log.\n', - fields: [ - { - name: 'client', - type: 'keyword', - description: "The client's version string.\n", - }, - { - name: 'direction', - type: 'keyword', - description: - 'Direction of the connection. If the client was a local host logging into\nan external host, this would be OUTBOUND. INBOUND would be set for the\nopposite situation.\n', - }, - { - name: 'host_key', - type: 'keyword', - description: "The server's key thumbprint.\n", - }, - { - name: 'server', - type: 'keyword', - description: "The server's version string.\n", - }, - { - name: 'version', - type: 'integer', - description: 'SSH major version (1 or 2).\n', - }, - { - name: 'algorithm', - type: 'group', - description: 'Cipher algorithms used in this session.\n', - fields: [ - { - name: 'cipher', - type: 'keyword', - description: 'The encryption algorithm in use.\n', - }, - { - name: 'compression', - type: 'keyword', - description: 'The compression algorithm in use.\n', - }, - { - name: 'host_key', - type: 'keyword', - description: "The server host key's algorithm.\n", - }, - { - name: 'key_exchange', - type: 'keyword', - description: 'The key exchange algorithm in use.\n', - }, - { - name: 'mac', - type: 'keyword', - description: 'The signing (MAC) algorithm in use.\n', - }, - ], - }, - { - name: 'auth', - type: 'group', - fields: [ - { - name: 'attempts', - type: 'integer', - description: - "The number of authentication attemps we observed. There's always at\nleast one, since some servers might support no authentication at all.\nIt's important to note that not all of these are failures, since some\nservers require two-factor auth (e.g. password AND pubkey).\n", - }, - { - name: 'success', - type: 'boolean', - description: 'Authentication result.\n', - }, - ], - }, - ], - }, - { - name: 'ssl', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek SSL log.\n', - fields: [ - { - name: 'version', - type: 'keyword', - description: 'SSL/TLS version that was logged.\n', - }, - { - name: 'cipher', - type: 'keyword', - description: 'SSL/TLS cipher suite that was logged.\n', - }, - { - name: 'curve', - type: 'keyword', - description: 'Elliptic curve that was logged when using ECDH/ECDHE.\n', - }, - { - name: 'resumed', - type: 'boolean', - description: - 'Flag to indicate if the session was resumed reusing the key material exchanged in an\nearlier connection.\n', - }, - { - name: 'next_protocol', - type: 'keyword', - description: - 'Next protocol the server chose using the application layer next protocol extension.\n', - }, - { - name: 'established', - type: 'boolean', - description: - 'Flag to indicate if this ssl session has been established successfully.\n', - }, - { - name: 'validation', - type: 'group', - fields: [ - { - name: 'status', - type: 'keyword', - description: 'Result of certificate validation for this connection.\n', - }, - { - name: 'code', - type: 'keyword', - description: - 'Result of certificate validation for this connection, given as OpenSSL validation code.\n', - }, - ], - }, - { - name: 'last_alert', - type: 'keyword', - description: 'Last alert that was seen during the connection.\n', - }, - { - name: 'server', - type: 'group', - fields: [ - { - name: 'name', - type: 'keyword', - description: - 'Value of the Server Name Indicator SSL/TLS extension. It indicates the server name\nthat the client was requesting.\n', - }, - { - name: 'cert_chain', - type: 'keyword', - description: - 'Chain of certificates offered by the server to validate its complete signing chain.\n', - }, - { - name: 'cert_chain_fuids', - type: 'keyword', - description: - 'An ordered vector of certificate file identifiers for the certificates offered by the server.\n', - }, - { - name: 'issuer', - type: 'group', - description: - 'Subject of the signer of the X.509 certificate offered by the server.\n', - fields: [ - { - name: 'common_name', - type: 'keyword', - description: - 'Common name of the signer of the X.509 certificate offered by the server.\n', - }, - { - name: 'country', - type: 'keyword', - description: - 'Country code of the signer of the X.509 certificate offered by the server.\n', - }, - { - name: 'locality', - type: 'keyword', - description: - 'Locality of the signer of the X.509 certificate offered by the server.\n', - }, - { - name: 'organization', - type: 'keyword', - description: - 'Organization of the signer of the X.509 certificate offered by the server.\n', - }, - { - name: 'organizational_unit', - type: 'keyword', - description: - 'Organizational unit of the signer of the X.509 certificate offered by the server.\n', - }, - { - name: 'state', - type: 'keyword', - description: - 'State or province name of the signer of the X.509 certificate offered by the server.\n', - }, - ], - }, - { - name: 'subject', - type: 'group', - description: 'Subject of the X.509 certificate offered by the server.\n', - fields: [ - { - name: 'common_name', - type: 'keyword', - description: - 'Common name of the X.509 certificate offered by the server.\n', - }, - { - name: 'country', - type: 'keyword', - description: - 'Country code of the X.509 certificate offered by the server.\n', - }, - { - name: 'locality', - type: 'keyword', - description: 'Locality of the X.509 certificate offered by the server.\n', - }, - { - name: 'organization', - type: 'keyword', - description: - 'Organization of the X.509 certificate offered by the server.\n', - }, - { - name: 'organizational_unit', - type: 'keyword', - description: - 'Organizational unit of the X.509 certificate offered by the server.\n', - }, - { - name: 'state', - type: 'keyword', - description: - 'State or province name of the X.509 certificate offered by the server.\n', - }, - ], - }, - ], - }, - { - name: 'client', - type: 'group', - fields: [ - { - name: 'cert_chain', - type: 'keyword', - description: - 'Chain of certificates offered by the client to validate its complete signing chain.\n', - }, - { - name: 'cert_chain_fuids', - type: 'keyword', - description: - 'An ordered vector of certificate file identifiers for the certificates offered by the client.\n', - }, - { - name: 'issuer', - type: 'group', - description: - 'Subject of the signer of the X.509 certificate offered by the client.\n', - fields: [ - { - name: 'common_name', - type: 'keyword', - description: - 'Common name of the signer of the X.509 certificate offered by the client.\n', - }, - { - name: 'country', - type: 'keyword', - description: - 'Country code of the signer of the X.509 certificate offered by the client.\n', - }, - { - name: 'locality', - type: 'keyword', - description: - 'Locality of the signer of the X.509 certificate offered by the client.\n', - }, - { - name: 'organization', - type: 'keyword', - description: - 'Organization of the signer of the X.509 certificate offered by the client.\n', - }, - { - name: 'organizational_unit', - type: 'keyword', - description: - 'Organizational unit of the signer of the X.509 certificate offered by the client.\n', - }, - { - name: 'state', - type: 'keyword', - description: - 'State or province name of the signer of the X.509 certificate offered by the client.\n', - }, - ], - }, - { - name: 'subject', - type: 'group', - description: 'Subject of the X.509 certificate offered by the client.\n', - fields: [ - { - name: 'common_name', - type: 'keyword', - description: - 'Common name of the X.509 certificate offered by the client.\n', - }, - { - name: 'country', - type: 'keyword', - description: - 'Country code of the X.509 certificate offered by the client.\n', - }, - { - name: 'locality', - type: 'keyword', - description: 'Locality of the X.509 certificate offered by the client.\n', - }, - { - name: 'organization', - type: 'keyword', - description: - 'Organization of the X.509 certificate offered by the client.\n', - }, - { - name: 'organizational_unit', - type: 'keyword', - description: - 'Organizational unit of the X.509 certificate offered by the client.\n', - }, - { - name: 'state', - type: 'keyword', - description: - 'State or province name of the X.509 certificate offered by the client.\n', - }, - ], - }, - ], - }, - ], - }, - { - name: 'stats', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek stats log.\n', - fields: [ - { - name: 'peer', - type: 'keyword', - description: 'Peer that generated this log. Mostly for clusters.\n', - }, - { - name: 'memory', - type: 'integer', - description: 'Amount of memory currently in use in MB.\n', - }, - { - name: 'packets', - type: 'group', - fields: [ - { - name: 'processed', - type: 'long', - description: 'Number of packets processed since the last stats interval.\n', - }, - { - name: 'dropped', - type: 'long', - description: - 'Number of packets dropped since the last stats interval if reading live traffic.\n', - }, - { - name: 'received', - type: 'long', - description: - 'Number of packets seen on the link since the last stats interval if reading live traffic.\n', - }, - ], - }, - { - name: 'bytes', - type: 'group', - fields: [ - { - name: 'received', - type: 'long', - description: - 'Number of bytes received since the last stats interval if reading live traffic.\n', - }, - ], - }, - { - name: 'connections', - type: 'group', - fields: [ - { - name: 'tcp', - type: 'group', - fields: [ - { - name: 'active', - type: 'integer', - description: 'TCP connections currently in memory.\n', - }, - { - name: 'count', - type: 'integer', - description: 'TCP connections seen since last stats interval.\n', - }, - ], - }, - { - name: 'udp', - type: 'group', - fields: [ - { - name: 'active', - type: 'integer', - description: 'UDP connections currently in memory.\n', - }, - { - name: 'count', - type: 'integer', - description: 'UDP connections seen since last stats interval.\n', - }, - ], - }, - { - name: 'icmp', - type: 'group', - fields: [ - { - name: 'active', - type: 'integer', - description: 'ICMP connections currently in memory.\n', - }, - { - name: 'count', - type: 'integer', - description: 'ICMP connections seen since last stats interval.\n', - }, - ], - }, - ], - }, - { - name: 'events', - type: 'group', - fields: [ - { - name: 'processed', - type: 'integer', - description: 'Number of events processed since the last stats interval.\n', - }, - { - name: 'queued', - type: 'integer', - description: - 'Number of events that have been queued since the last stats interval.\n', - }, - ], - }, - { - name: 'timers', - type: 'group', - fields: [ - { - name: 'count', - type: 'integer', - description: 'Number of timers scheduled since last stats interval.\n', - }, - { - name: 'active', - type: 'integer', - description: 'Current number of scheduled timers.\n', - }, - ], - }, - { - name: 'files', - type: 'group', - fields: [ - { - name: 'count', - type: 'integer', - description: 'Number of files seen since last stats interval.\n', - }, - { - name: 'active', - type: 'integer', - description: 'Current number of files actively being seen.\n', - }, - ], - }, - { - name: 'dns_requests', - type: 'group', - fields: [ - { - name: 'count', - type: 'integer', - description: 'Number of DNS requests seen since last stats interval.\n', - }, - { - name: 'active', - type: 'integer', - description: 'Current number of DNS requests awaiting a reply.\n', - }, - ], - }, - { - name: 'reassembly_size', - type: 'group', - fields: [ - { - name: 'tcp', - type: 'integer', - description: 'Current size of TCP data in reassembly.\n', - }, - { - name: 'file', - type: 'integer', - description: 'Current size of File data in reassembly.\n', - }, - { - name: 'frag', - type: 'integer', - description: 'Current size of packet fragment data in reassembly.\n', - }, - { - name: 'unknown', - type: 'integer', - description: - 'Current size of unknown data in reassembly (this is only PIA buffer right now).\n', - }, - ], - }, - { - name: 'timestamp_lag', - type: 'integer', - description: - 'Lag between the wall clock and packet timestamps if reading live traffic.\n', - }, - ], - }, - { - name: 'syslog', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek syslog log.\n', - fields: [ - { - name: 'facility', - type: 'keyword', - description: 'Syslog facility for the message.\n', - }, - { - name: 'severity', - type: 'keyword', - description: 'Syslog severity for the message.\n', - }, - { - name: 'message', - type: 'keyword', - description: 'The plain text message.\n', - }, - ], - }, - { - name: 'tunnel', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek SSH log.\n', - fields: [ - { - name: 'type', - type: 'keyword', - description: 'The type of tunnel.\n', - }, - { - name: 'action', - type: 'keyword', - description: 'The type of activity that occurred.\n', - }, - ], - }, - { - name: 'weird', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek Weird log.\n', - fields: [ - { - name: 'name', - type: 'keyword', - description: 'The name of the weird that occurred.\n', - }, - { - name: 'additional_info', - type: 'keyword', - description: 'Additional information accompanying the weird if any.\n', - }, - { - name: 'notice', - type: 'boolean', - description: 'Indicate if this weird was also turned into a notice.\n', - }, - { - name: 'peer', - type: 'keyword', - description: - 'The peer that originated this weird. This is helpful in cluster deployments if a particular cluster node is having trouble to help identify which node is having trouble.\n', - }, - { - name: 'identifier', - type: 'keyword', - description: - 'This field is to be provided when a weird is generated for the purpose of deduplicating weirds. The identifier string should be unique for a single instance of the weird. This field is used to define when a weird is conceptually a duplicate of a previous weird.\n', - }, - ], - }, - { - name: 'x509', - type: 'group', - default_field: false, - description: 'Fields exported by the Zeek x509 log.\n', - fields: [ - { - name: 'id', - type: 'keyword', - description: 'File id of this certificate.\n', - }, - { - name: 'certificate', - type: 'group', - description: 'Basic information about the certificate.\n', - fields: [ - { - name: 'version', - type: 'integer', - description: 'Version number.\n', - }, - { - name: 'serial', - type: 'keyword', - description: 'Serial number.\n', - }, - { - name: 'subject', - type: 'group', - description: 'Subject.\n', - fields: [ - { - name: 'country', - type: 'keyword', - description: 'Country provided in the certificate subject.\n', - }, - { - name: 'common_name', - type: 'keyword', - description: 'Common name provided in the certificate subject.\n', - }, - { - name: 'locality', - type: 'keyword', - description: 'Locality provided in the certificate subject.\n', - }, - { - name: 'organization', - type: 'keyword', - description: 'Organization provided in the certificate subject.\n', - }, - { - name: 'organizational_unit', - type: 'keyword', - description: 'Organizational unit provided in the certificate subject.\n', - }, - { - name: 'state', - type: 'keyword', - description: 'State or province provided in the certificate subject.\n', - }, - ], - }, - { - name: 'issuer', - type: 'group', - description: 'Issuer.\n', - fields: [ - { - name: 'country', - type: 'keyword', - description: 'Country provided in the certificate issuer field.\n', - }, - { - name: 'common_name', - type: 'keyword', - description: 'Common name provided in the certificate issuer field.\n', - }, - { - name: 'locality', - type: 'keyword', - description: 'Locality provided in the certificate issuer field.\n', - }, - { - name: 'organization', - type: 'keyword', - description: 'Organization provided in the certificate issuer field.\n', - }, - { - name: 'organizational_unit', - type: 'keyword', - description: - 'Organizational unit provided in the certificate issuer field.\n', - }, - { - name: 'state', - type: 'keyword', - description: - 'State or province provided in the certificate issuer field.\n', - }, - ], - }, - { - name: 'common_name', - type: 'keyword', - description: 'Last (most specific) common name.\n', - }, - { - name: 'valid', - type: 'group', - description: 'Certificate validity timestamps\n', - fields: [ - { - name: 'from', - type: 'date', - description: 'Timestamp before when certificate is not valid.\n', - }, - { - name: 'until', - type: 'date', - description: 'Timestamp after when certificate is not valid.\n', - }, - ], - }, - { - name: 'key', - type: 'group', - fields: [ - { - name: 'algorithm', - type: 'keyword', - description: 'Name of the key algorithm.\n', - }, - { - name: 'type', - type: 'keyword', - description: - 'Key type, if key parseable by openssl (either rsa, dsa or ec).\n', - }, - { - name: 'length', - type: 'integer', - description: 'Key length in bits.\n', - }, - ], - }, - { - name: 'signature_algorithm', - type: 'keyword', - description: 'Name of the signature algorithm.\n', - }, - { - name: 'exponent', - type: 'keyword', - description: 'Exponent, if RSA-certificate.\n', - }, - { - name: 'curve', - type: 'keyword', - description: 'Curve, if EC-certificate.\n', - }, - ], - }, - { - name: 'san', - type: 'group', - description: 'Subject alternative name extension of the certificate.\n', - fields: [ - { - name: 'dns', - type: 'keyword', - description: 'List of DNS entries in SAN.\n', - }, - { - name: 'uri', - type: 'keyword', - description: 'List of URI entries in SAN.\n', - }, - { - name: 'email', - type: 'keyword', - description: 'List of email entries in SAN.\n', - }, - { - name: 'ip', - type: 'ip', - description: 'List of IP entries in SAN.\n', - }, - { - name: 'other_fields', - type: 'boolean', - description: - 'True if the certificate contained other, not recognized or parsed name fields.\n', - }, - ], - }, - { - name: 'basic_constraints', - type: 'group', - description: 'Basic constraints extension of the certificate.\n', - fields: [ - { - name: 'certificate_authority', - type: 'boolean', - description: 'CA flag set or not.\n', - }, - { - name: 'path_length', - type: 'integer', - description: 'Maximum path length.\n', - }, - ], - }, - { - name: 'log_cert', - type: 'boolean', - description: - 'Present if policy/protocols/ssl/log-hostcerts-only.bro is loaded\nLogging of certificate is suppressed if set to F.\n', - }, - ], - }, - ], - }, - ], - }, - { - key: 'netflow', - title: 'NetFlow', - description: 'Fields from NetFlow and IPFIX flows.\n', - fields: [ - { - name: 'netflow', - type: 'group', - description: 'Fields from NetFlow and IPFIX.\n', - fields: [ - { - name: 'type', - type: 'keyword', - description: 'The type of NetFlow record described by this event.\n', - }, - { - name: 'exporter', - type: 'group', - description: 'Metadata related to the exporter device that generated this record.\n', - fields: [ - { - name: 'address', - type: 'keyword', - description: "Exporter's network address in IP:port format.\n", - }, - { - name: 'source_id', - type: 'long', - description: 'Observation domain ID to which this record belongs.\n', - }, - { - name: 'timestamp', - type: 'date', - description: 'Time and date of export.\n', - }, - { - name: 'uptime_millis', - type: 'long', - description: 'How long the exporter process has been running, in milliseconds.\n', - }, - { - name: 'version', - type: 'integer', - description: 'NetFlow version used.\n', - }, - ], - }, - { - name: 'octet_delta_count', - type: 'long', - }, - { - name: 'packet_delta_count', - type: 'long', - }, - { - name: 'delta_flow_count', - type: 'long', - }, - { - name: 'protocol_identifier', - type: 'short', - }, - { - name: 'ip_class_of_service', - type: 'short', - }, - { - name: 'tcp_control_bits', - type: 'integer', - }, - { - name: 'source_transport_port', - type: 'integer', - }, - { - name: 'source_ipv4_address', - type: 'ip', - }, - { - name: 'source_ipv4_prefix_length', - type: 'short', - }, - { - name: 'ingress_interface', - type: 'long', - }, - { - name: 'destination_transport_port', - type: 'integer', - }, - { - name: 'destination_ipv4_address', - type: 'ip', - }, - { - name: 'destination_ipv4_prefix_length', - type: 'short', - }, - { - name: 'egress_interface', - type: 'long', - }, - { - name: 'ip_next_hop_ipv4_address', - type: 'ip', - }, - { - name: 'bgp_source_as_number', - type: 'long', - }, - { - name: 'bgp_destination_as_number', - type: 'long', - }, - { - name: 'bgp_next_hop_ipv4_address', - type: 'ip', - }, - { - name: 'post_mcast_packet_delta_count', - type: 'long', - }, - { - name: 'post_mcast_octet_delta_count', - type: 'long', - }, - { - name: 'flow_end_sys_up_time', - type: 'long', - }, - { - name: 'flow_start_sys_up_time', - type: 'long', - }, - { - name: 'post_octet_delta_count', - type: 'long', - }, - { - name: 'post_packet_delta_count', - type: 'long', - }, - { - name: 'minimum_ip_total_length', - type: 'long', - }, - { - name: 'maximum_ip_total_length', - type: 'long', - }, - { - name: 'source_ipv6_address', - type: 'ip', - }, - { - name: 'destination_ipv6_address', - type: 'ip', - }, - { - name: 'source_ipv6_prefix_length', - type: 'short', - }, - { - name: 'destination_ipv6_prefix_length', - type: 'short', - }, - { - name: 'flow_label_ipv6', - type: 'long', - }, - { - name: 'icmp_type_code_ipv4', - type: 'integer', - }, - { - name: 'igmp_type', - type: 'short', - }, - { - name: 'sampling_interval', - type: 'long', - }, - { - name: 'sampling_algorithm', - type: 'short', - }, - { - name: 'flow_active_timeout', - type: 'integer', - }, - { - name: 'flow_idle_timeout', - type: 'integer', - }, - { - name: 'engine_type', - type: 'short', - }, - { - name: 'engine_id', - type: 'short', - }, - { - name: 'exported_octet_total_count', - type: 'long', - }, - { - name: 'exported_message_total_count', - type: 'long', - }, - { - name: 'exported_flow_record_total_count', - type: 'long', - }, - { - name: 'ipv4_router_sc', - type: 'ip', - }, - { - name: 'source_ipv4_prefix', - type: 'ip', - }, - { - name: 'destination_ipv4_prefix', - type: 'ip', - }, - { - name: 'mpls_top_label_type', - type: 'short', - }, - { - name: 'mpls_top_label_ipv4_address', - type: 'ip', - }, - { - name: 'sampler_id', - type: 'short', - }, - { - name: 'sampler_mode', - type: 'short', - }, - { - name: 'sampler_random_interval', - type: 'long', - }, - { - name: 'class_id', - type: 'long', - }, - { - name: 'minimum_ttl', - type: 'short', - }, - { - name: 'maximum_ttl', - type: 'short', - }, - { - name: 'fragment_identification', - type: 'long', - }, - { - name: 'post_ip_class_of_service', - type: 'short', - }, - { - name: 'source_mac_address', - type: 'keyword', - }, - { - name: 'post_destination_mac_address', - type: 'keyword', - }, - { - name: 'vlan_id', - type: 'integer', - }, - { - name: 'post_vlan_id', - type: 'integer', - }, - { - name: 'ip_version', - type: 'short', - }, - { - name: 'flow_direction', - type: 'short', - }, - { - name: 'ip_next_hop_ipv6_address', - type: 'ip', - }, - { - name: 'bgp_next_hop_ipv6_address', - type: 'ip', - }, - { - name: 'ipv6_extension_headers', - type: 'long', - }, - { - name: 'mpls_top_label_stack_section', - type: 'short', - }, - { - name: 'mpls_label_stack_section2', - type: 'short', - }, - { - name: 'mpls_label_stack_section3', - type: 'short', - }, - { - name: 'mpls_label_stack_section4', - type: 'short', - }, - { - name: 'mpls_label_stack_section5', - type: 'short', - }, - { - name: 'mpls_label_stack_section6', - type: 'short', - }, - { - name: 'mpls_label_stack_section7', - type: 'short', - }, - { - name: 'mpls_label_stack_section8', - type: 'short', - }, - { - name: 'mpls_label_stack_section9', - type: 'short', - }, - { - name: 'mpls_label_stack_section10', - type: 'short', - }, - { - name: 'destination_mac_address', - type: 'keyword', - }, - { - name: 'post_source_mac_address', - type: 'keyword', - }, - { - name: 'interface_name', - type: 'keyword', - }, - { - name: 'interface_description', - type: 'keyword', - }, - { - name: 'sampler_name', - type: 'keyword', - }, - { - name: 'octet_total_count', - type: 'long', - }, - { - name: 'packet_total_count', - type: 'long', - }, - { - name: 'flags_and_sampler_id', - type: 'long', - }, - { - name: 'fragment_offset', - type: 'integer', - }, - { - name: 'forwarding_status', - type: 'short', - }, - { - name: 'mpls_vpn_route_distinguisher', - type: 'short', - }, - { - name: 'mpls_top_label_prefix_length', - type: 'short', - }, - { - name: 'src_traffic_index', - type: 'long', - }, - { - name: 'dst_traffic_index', - type: 'long', - }, - { - name: 'application_description', - type: 'keyword', - }, - { - name: 'application_id', - type: 'short', - }, - { - name: 'application_name', - type: 'keyword', - }, - { - name: 'post_ip_diff_serv_code_point', - type: 'short', - }, - { - name: 'multicast_replication_factor', - type: 'long', - }, - { - name: 'class_name', - type: 'keyword', - }, - { - name: 'classification_engine_id', - type: 'short', - }, - { - name: 'layer2packet_section_offset', - type: 'integer', - }, - { - name: 'layer2packet_section_size', - type: 'integer', - }, - { - name: 'layer2packet_section_data', - type: 'short', - }, - { - name: 'bgp_next_adjacent_as_number', - type: 'long', - }, - { - name: 'bgp_prev_adjacent_as_number', - type: 'long', - }, - { - name: 'exporter_ipv4_address', - type: 'ip', - }, - { - name: 'exporter_ipv6_address', - type: 'ip', - }, - { - name: 'dropped_octet_delta_count', - type: 'long', - }, - { - name: 'dropped_packet_delta_count', - type: 'long', - }, - { - name: 'dropped_octet_total_count', - type: 'long', - }, - { - name: 'dropped_packet_total_count', - type: 'long', - }, - { - name: 'flow_end_reason', - type: 'short', - }, - { - name: 'common_properties_id', - type: 'long', - }, - { - name: 'observation_point_id', - type: 'long', - }, - { - name: 'icmp_type_code_ipv6', - type: 'integer', - }, - { - name: 'mpls_top_label_ipv6_address', - type: 'ip', - }, - { - name: 'line_card_id', - type: 'long', - }, - { - name: 'port_id', - type: 'long', - }, - { - name: 'metering_process_id', - type: 'long', - }, - { - name: 'exporting_process_id', - type: 'long', - }, - { - name: 'template_id', - type: 'integer', - }, - { - name: 'wlan_channel_id', - type: 'short', - }, - { - name: 'wlan_ssid', - type: 'keyword', - }, - { - name: 'flow_id', - type: 'long', - }, - { - name: 'observation_domain_id', - type: 'long', - }, - { - name: 'flow_start_seconds', - type: 'date', - }, - { - name: 'flow_end_seconds', - type: 'date', - }, - { - name: 'flow_start_milliseconds', - type: 'date', - }, - { - name: 'flow_end_milliseconds', - type: 'date', - }, - { - name: 'flow_start_microseconds', - type: 'date', - }, - { - name: 'flow_end_microseconds', - type: 'date', - }, - { - name: 'flow_start_nanoseconds', - type: 'date', - }, - { - name: 'flow_end_nanoseconds', - type: 'date', - }, - { - name: 'flow_start_delta_microseconds', - type: 'long', - }, - { - name: 'flow_end_delta_microseconds', - type: 'long', - }, - { - name: 'system_init_time_milliseconds', - type: 'date', - }, - { - name: 'flow_duration_milliseconds', - type: 'long', - }, - { - name: 'flow_duration_microseconds', - type: 'long', - }, - { - name: 'observed_flow_total_count', - type: 'long', - }, - { - name: 'ignored_packet_total_count', - type: 'long', - }, - { - name: 'ignored_octet_total_count', - type: 'long', - }, - { - name: 'not_sent_flow_total_count', - type: 'long', - }, - { - name: 'not_sent_packet_total_count', - type: 'long', - }, - { - name: 'not_sent_octet_total_count', - type: 'long', - }, - { - name: 'destination_ipv6_prefix', - type: 'ip', - }, - { - name: 'source_ipv6_prefix', - type: 'ip', - }, - { - name: 'post_octet_total_count', - type: 'long', - }, - { - name: 'post_packet_total_count', - type: 'long', - }, - { - name: 'flow_key_indicator', - type: 'long', - }, - { - name: 'post_mcast_packet_total_count', - type: 'long', - }, - { - name: 'post_mcast_octet_total_count', - type: 'long', - }, - { - name: 'icmp_type_ipv4', - type: 'short', - }, - { - name: 'icmp_code_ipv4', - type: 'short', - }, - { - name: 'icmp_type_ipv6', - type: 'short', - }, - { - name: 'icmp_code_ipv6', - type: 'short', - }, - { - name: 'udp_source_port', - type: 'integer', - }, - { - name: 'udp_destination_port', - type: 'integer', - }, - { - name: 'tcp_source_port', - type: 'integer', - }, - { - name: 'tcp_destination_port', - type: 'integer', - }, - { - name: 'tcp_sequence_number', - type: 'long', - }, - { - name: 'tcp_acknowledgement_number', - type: 'long', - }, - { - name: 'tcp_window_size', - type: 'integer', - }, - { - name: 'tcp_urgent_pointer', - type: 'integer', - }, - { - name: 'tcp_header_length', - type: 'short', - }, - { - name: 'ip_header_length', - type: 'short', - }, - { - name: 'total_length_ipv4', - type: 'integer', - }, - { - name: 'payload_length_ipv6', - type: 'integer', - }, - { - name: 'ip_ttl', - type: 'short', - }, - { - name: 'next_header_ipv6', - type: 'short', - }, - { - name: 'mpls_payload_length', - type: 'long', - }, - { - name: 'ip_diff_serv_code_point', - type: 'short', - }, - { - name: 'ip_precedence', - type: 'short', - }, - { - name: 'fragment_flags', - type: 'short', - }, - { - name: 'octet_delta_sum_of_squares', - type: 'long', - }, - { - name: 'octet_total_sum_of_squares', - type: 'long', - }, - { - name: 'mpls_top_label_ttl', - type: 'short', - }, - { - name: 'mpls_label_stack_length', - type: 'long', - }, - { - name: 'mpls_label_stack_depth', - type: 'long', - }, - { - name: 'mpls_top_label_exp', - type: 'short', - }, - { - name: 'ip_payload_length', - type: 'long', - }, - { - name: 'udp_message_length', - type: 'integer', - }, - { - name: 'is_multicast', - type: 'short', - }, - { - name: 'ipv4_ihl', - type: 'short', - }, - { - name: 'ipv4_options', - type: 'long', - }, - { - name: 'tcp_options', - type: 'long', - }, - { - name: 'padding_octets', - type: 'short', - }, - { - name: 'collector_ipv4_address', - type: 'ip', - }, - { - name: 'collector_ipv6_address', - type: 'ip', - }, - { - name: 'export_interface', - type: 'long', - }, - { - name: 'export_protocol_version', - type: 'short', - }, - { - name: 'export_transport_protocol', - type: 'short', - }, - { - name: 'collector_transport_port', - type: 'integer', - }, - { - name: 'exporter_transport_port', - type: 'integer', - }, - { - name: 'tcp_syn_total_count', - type: 'long', - }, - { - name: 'tcp_fin_total_count', - type: 'long', - }, - { - name: 'tcp_rst_total_count', - type: 'long', - }, - { - name: 'tcp_psh_total_count', - type: 'long', - }, - { - name: 'tcp_ack_total_count', - type: 'long', - }, - { - name: 'tcp_urg_total_count', - type: 'long', - }, - { - name: 'ip_total_length', - type: 'long', - }, - { - name: 'post_nat_source_ipv4_address', - type: 'ip', - }, - { - name: 'post_nat_destination_ipv4_address', - type: 'ip', - }, - { - name: 'post_napt_source_transport_port', - type: 'integer', - }, - { - name: 'post_napt_destination_transport_port', - type: 'integer', - }, - { - name: 'nat_originating_address_realm', - type: 'short', - }, - { - name: 'nat_event', - type: 'short', - }, - { - name: 'initiator_octets', - type: 'long', - }, - { - name: 'responder_octets', - type: 'long', - }, - { - name: 'firewall_event', - type: 'short', - }, - { - name: 'ingress_vrfid', - type: 'long', - }, - { - name: 'egress_vrfid', - type: 'long', - }, - { - name: 'vr_fname', - type: 'keyword', - }, - { - name: 'post_mpls_top_label_exp', - type: 'short', - }, - { - name: 'tcp_window_scale', - type: 'integer', - }, - { - name: 'biflow_direction', - type: 'short', - }, - { - name: 'ethernet_header_length', - type: 'short', - }, - { - name: 'ethernet_payload_length', - type: 'integer', - }, - { - name: 'ethernet_total_length', - type: 'integer', - }, - { - name: 'dot1q_vlan_id', - type: 'integer', - }, - { - name: 'dot1q_priority', - type: 'short', - }, - { - name: 'dot1q_customer_vlan_id', - type: 'integer', - }, - { - name: 'dot1q_customer_priority', - type: 'short', - }, - { - name: 'metro_evc_id', - type: 'keyword', - }, - { - name: 'metro_evc_type', - type: 'short', - }, - { - name: 'pseudo_wire_id', - type: 'long', - }, - { - name: 'pseudo_wire_type', - type: 'integer', - }, - { - name: 'pseudo_wire_control_word', - type: 'long', - }, - { - name: 'ingress_physical_interface', - type: 'long', - }, - { - name: 'egress_physical_interface', - type: 'long', - }, - { - name: 'post_dot1q_vlan_id', - type: 'integer', - }, - { - name: 'post_dot1q_customer_vlan_id', - type: 'integer', - }, - { - name: 'ethernet_type', - type: 'integer', - }, - { - name: 'post_ip_precedence', - type: 'short', - }, - { - name: 'collection_time_milliseconds', - type: 'date', - }, - { - name: 'export_sctp_stream_id', - type: 'integer', - }, - { - name: 'max_export_seconds', - type: 'date', - }, - { - name: 'max_flow_end_seconds', - type: 'date', - }, - { - name: 'message_md5_checksum', - type: 'short', - }, - { - name: 'message_scope', - type: 'short', - }, - { - name: 'min_export_seconds', - type: 'date', - }, - { - name: 'min_flow_start_seconds', - type: 'date', - }, - { - name: 'opaque_octets', - type: 'short', - }, - { - name: 'session_scope', - type: 'short', - }, - { - name: 'max_flow_end_microseconds', - type: 'date', - }, - { - name: 'max_flow_end_milliseconds', - type: 'date', - }, - { - name: 'max_flow_end_nanoseconds', - type: 'date', - }, - { - name: 'min_flow_start_microseconds', - type: 'date', - }, - { - name: 'min_flow_start_milliseconds', - type: 'date', - }, - { - name: 'min_flow_start_nanoseconds', - type: 'date', - }, - { - name: 'collector_certificate', - type: 'short', - }, - { - name: 'exporter_certificate', - type: 'short', - }, - { - name: 'data_records_reliability', - type: 'boolean', - }, - { - name: 'observation_point_type', - type: 'short', - }, - { - name: 'new_connection_delta_count', - type: 'long', - }, - { - name: 'connection_sum_duration_seconds', - type: 'long', - }, - { - name: 'connection_transaction_id', - type: 'long', - }, - { - name: 'post_nat_source_ipv6_address', - type: 'ip', - }, - { - name: 'post_nat_destination_ipv6_address', - type: 'ip', - }, - { - name: 'nat_pool_id', - type: 'long', - }, - { - name: 'nat_pool_name', - type: 'keyword', - }, - { - name: 'anonymization_flags', - type: 'integer', - }, - { - name: 'anonymization_technique', - type: 'integer', - }, - { - name: 'information_element_index', - type: 'integer', - }, - { - name: 'p2p_technology', - type: 'keyword', - }, - { - name: 'tunnel_technology', - type: 'keyword', - }, - { - name: 'encrypted_technology', - type: 'keyword', - }, - { - name: 'bgp_validity_state', - type: 'short', - }, - { - name: 'ip_sec_spi', - type: 'long', - }, - { - name: 'gre_key', - type: 'long', - }, - { - name: 'nat_type', - type: 'short', - }, - { - name: 'initiator_packets', - type: 'long', - }, - { - name: 'responder_packets', - type: 'long', - }, - { - name: 'observation_domain_name', - type: 'keyword', - }, - { - name: 'selection_sequence_id', - type: 'long', - }, - { - name: 'selector_id', - type: 'long', - }, - { - name: 'information_element_id', - type: 'integer', - }, - { - name: 'selector_algorithm', - type: 'integer', - }, - { - name: 'sampling_packet_interval', - type: 'long', - }, - { - name: 'sampling_packet_space', - type: 'long', - }, - { - name: 'sampling_time_interval', - type: 'long', - }, - { - name: 'sampling_time_space', - type: 'long', - }, - { - name: 'sampling_size', - type: 'long', - }, - { - name: 'sampling_population', - type: 'long', - }, - { - name: 'sampling_probability', - type: 'double', - }, - { - name: 'data_link_frame_size', - type: 'integer', - }, - { - name: 'ip_header_packet_section', - type: 'short', - }, - { - name: 'ip_payload_packet_section', - type: 'short', - }, - { - name: 'data_link_frame_section', - type: 'short', - }, - { - name: 'mpls_label_stack_section', - type: 'short', - }, - { - name: 'mpls_payload_packet_section', - type: 'short', - }, - { - name: 'selector_id_total_pkts_observed', - type: 'long', - }, - { - name: 'selector_id_total_pkts_selected', - type: 'long', - }, - { - name: 'absolute_error', - type: 'double', - }, - { - name: 'relative_error', - type: 'double', - }, - { - name: 'observation_time_seconds', - type: 'date', - }, - { - name: 'observation_time_milliseconds', - type: 'date', - }, - { - name: 'observation_time_microseconds', - type: 'date', - }, - { - name: 'observation_time_nanoseconds', - type: 'date', - }, - { - name: 'digest_hash_value', - type: 'long', - }, - { - name: 'hash_ip_payload_offset', - type: 'long', - }, - { - name: 'hash_ip_payload_size', - type: 'long', - }, - { - name: 'hash_output_range_min', - type: 'long', - }, - { - name: 'hash_output_range_max', - type: 'long', - }, - { - name: 'hash_selected_range_min', - type: 'long', - }, - { - name: 'hash_selected_range_max', - type: 'long', - }, - { - name: 'hash_digest_output', - type: 'boolean', - }, - { - name: 'hash_initialiser_value', - type: 'long', - }, - { - name: 'selector_name', - type: 'keyword', - }, - { - name: 'upper_ci_limit', - type: 'double', - }, - { - name: 'lower_ci_limit', - type: 'double', - }, - { - name: 'confidence_level', - type: 'double', - }, - { - name: 'information_element_data_type', - type: 'short', - }, - { - name: 'information_element_description', - type: 'keyword', - }, - { - name: 'information_element_name', - type: 'keyword', - }, - { - name: 'information_element_range_begin', - type: 'long', - }, - { - name: 'information_element_range_end', - type: 'long', - }, - { - name: 'information_element_semantics', - type: 'short', - }, - { - name: 'information_element_units', - type: 'integer', - }, - { - name: 'private_enterprise_number', - type: 'long', - }, - { - name: 'virtual_station_interface_id', - type: 'short', - }, - { - name: 'virtual_station_interface_name', - type: 'keyword', - }, - { - name: 'virtual_station_uuid', - type: 'short', - }, - { - name: 'virtual_station_name', - type: 'keyword', - }, - { - name: 'layer2_segment_id', - type: 'long', - }, - { - name: 'layer2_octet_delta_count', - type: 'long', - }, - { - name: 'layer2_octet_total_count', - type: 'long', - }, - { - name: 'ingress_unicast_packet_total_count', - type: 'long', - }, - { - name: 'ingress_multicast_packet_total_count', - type: 'long', - }, - { - name: 'ingress_broadcast_packet_total_count', - type: 'long', - }, - { - name: 'egress_unicast_packet_total_count', - type: 'long', - }, - { - name: 'egress_broadcast_packet_total_count', - type: 'long', - }, - { - name: 'monitoring_interval_start_milli_seconds', - type: 'date', - }, - { - name: 'monitoring_interval_end_milli_seconds', - type: 'date', - }, - { - name: 'port_range_start', - type: 'integer', - }, - { - name: 'port_range_end', - type: 'integer', - }, - { - name: 'port_range_step_size', - type: 'integer', - }, - { - name: 'port_range_num_ports', - type: 'integer', - }, - { - name: 'sta_mac_address', - type: 'keyword', - }, - { - name: 'sta_ipv4_address', - type: 'ip', - }, - { - name: 'wtp_mac_address', - type: 'keyword', - }, - { - name: 'ingress_interface_type', - type: 'long', - }, - { - name: 'egress_interface_type', - type: 'long', - }, - { - name: 'rtp_sequence_number', - type: 'integer', - }, - { - name: 'user_name', - type: 'keyword', - }, - { - name: 'application_category_name', - type: 'keyword', - }, - { - name: 'application_sub_category_name', - type: 'keyword', - }, - { - name: 'application_group_name', - type: 'keyword', - }, - { - name: 'original_flows_present', - type: 'long', - }, - { - name: 'original_flows_initiated', - type: 'long', - }, - { - name: 'original_flows_completed', - type: 'long', - }, - { - name: 'distinct_count_of_source_ip_address', - type: 'long', - }, - { - name: 'distinct_count_of_destination_ip_address', - type: 'long', - }, - { - name: 'distinct_count_of_source_ipv4_address', - type: 'long', - }, - { - name: 'distinct_count_of_destination_ipv4_address', - type: 'long', - }, - { - name: 'distinct_count_of_source_ipv6_address', - type: 'long', - }, - { - name: 'distinct_count_of_destination_ipv6_address', - type: 'long', - }, - { - name: 'value_distribution_method', - type: 'short', - }, - { - name: 'rfc3550_jitter_milliseconds', - type: 'long', - }, - { - name: 'rfc3550_jitter_microseconds', - type: 'long', - }, - { - name: 'rfc3550_jitter_nanoseconds', - type: 'long', - }, - { - name: 'dot1q_dei', - type: 'boolean', - }, - { - name: 'dot1q_customer_dei', - type: 'boolean', - }, - { - name: 'flow_selector_algorithm', - type: 'integer', - }, - { - name: 'flow_selected_octet_delta_count', - type: 'long', - }, - { - name: 'flow_selected_packet_delta_count', - type: 'long', - }, - { - name: 'flow_selected_flow_delta_count', - type: 'long', - }, - { - name: 'selector_id_total_flows_observed', - type: 'long', - }, - { - name: 'selector_id_total_flows_selected', - type: 'long', - }, - { - name: 'sampling_flow_interval', - type: 'long', - }, - { - name: 'sampling_flow_spacing', - type: 'long', - }, - { - name: 'flow_sampling_time_interval', - type: 'long', - }, - { - name: 'flow_sampling_time_spacing', - type: 'long', - }, - { - name: 'hash_flow_domain', - type: 'integer', - }, - { - name: 'transport_octet_delta_count', - type: 'long', - }, - { - name: 'transport_packet_delta_count', - type: 'long', - }, - { - name: 'original_exporter_ipv4_address', - type: 'ip', - }, - { - name: 'original_exporter_ipv6_address', - type: 'ip', - }, - { - name: 'original_observation_domain_id', - type: 'long', - }, - { - name: 'intermediate_process_id', - type: 'long', - }, - { - name: 'ignored_data_record_total_count', - type: 'long', - }, - { - name: 'data_link_frame_type', - type: 'integer', - }, - { - name: 'section_offset', - type: 'integer', - }, - { - name: 'section_exported_octets', - type: 'integer', - }, - { - name: 'dot1q_service_instance_tag', - type: 'short', - }, - { - name: 'dot1q_service_instance_id', - type: 'long', - }, - { - name: 'dot1q_service_instance_priority', - type: 'short', - }, - { - name: 'dot1q_customer_source_mac_address', - type: 'keyword', - }, - { - name: 'dot1q_customer_destination_mac_address', - type: 'keyword', - }, - { - name: 'post_layer2_octet_delta_count', - type: 'long', - }, - { - name: 'post_mcast_layer2_octet_delta_count', - type: 'long', - }, - { - name: 'post_layer2_octet_total_count', - type: 'long', - }, - { - name: 'post_mcast_layer2_octet_total_count', - type: 'long', - }, - { - name: 'minimum_layer2_total_length', - type: 'long', - }, - { - name: 'maximum_layer2_total_length', - type: 'long', - }, - { - name: 'dropped_layer2_octet_delta_count', - type: 'long', - }, - { - name: 'dropped_layer2_octet_total_count', - type: 'long', - }, - { - name: 'ignored_layer2_octet_total_count', - type: 'long', - }, - { - name: 'not_sent_layer2_octet_total_count', - type: 'long', - }, - { - name: 'layer2_octet_delta_sum_of_squares', - type: 'long', - }, - { - name: 'layer2_octet_total_sum_of_squares', - type: 'long', - }, - { - name: 'layer2_frame_delta_count', - type: 'long', - }, - { - name: 'layer2_frame_total_count', - type: 'long', - }, - { - name: 'pseudo_wire_destination_ipv4_address', - type: 'ip', - }, - { - name: 'ignored_layer2_frame_total_count', - type: 'long', - }, - { - name: 'mib_object_value_integer', - type: 'integer', - }, - { - name: 'mib_object_value_octet_string', - type: 'short', - }, - { - name: 'mib_object_value_oid', - type: 'short', - }, - { - name: 'mib_object_value_bits', - type: 'short', - }, - { - name: 'mib_object_value_ip_address', - type: 'ip', - }, - { - name: 'mib_object_value_counter', - type: 'long', - }, - { - name: 'mib_object_value_gauge', - type: 'long', - }, - { - name: 'mib_object_value_time_ticks', - type: 'long', - }, - { - name: 'mib_object_value_unsigned', - type: 'long', - }, - { - name: 'mib_object_identifier', - type: 'short', - }, - { - name: 'mib_sub_identifier', - type: 'long', - }, - { - name: 'mib_index_indicator', - type: 'long', - }, - { - name: 'mib_capture_time_semantics', - type: 'short', - }, - { - name: 'mib_context_engine_id', - type: 'short', - }, - { - name: 'mib_context_name', - type: 'keyword', - }, - { - name: 'mib_object_name', - type: 'keyword', - }, - { - name: 'mib_object_description', - type: 'keyword', - }, - { - name: 'mib_object_syntax', - type: 'keyword', - }, - { - name: 'mib_module_name', - type: 'keyword', - }, - { - name: 'mobile_imsi', - type: 'keyword', - }, - { - name: 'mobile_msisdn', - type: 'keyword', - }, - { - name: 'http_status_code', - type: 'integer', - }, - { - name: 'source_transport_ports_limit', - type: 'integer', - }, - { - name: 'http_request_method', - type: 'keyword', - }, - { - name: 'http_request_host', - type: 'keyword', - }, - { - name: 'http_request_target', - type: 'keyword', - }, - { - name: 'http_message_version', - type: 'keyword', - }, - { - name: 'nat_instance_id', - type: 'long', - }, - { - name: 'internal_address_realm', - type: 'short', - }, - { - name: 'external_address_realm', - type: 'short', - }, - { - name: 'nat_quota_exceeded_event', - type: 'long', - }, - { - name: 'nat_threshold_event', - type: 'long', - }, - { - name: 'http_user_agent', - type: 'keyword', - }, - { - name: 'http_content_type', - type: 'keyword', - }, - { - name: 'http_reason_phrase', - type: 'keyword', - }, - { - name: 'max_session_entries', - type: 'long', - }, - { - name: 'max_bib_entries', - type: 'long', - }, - { - name: 'max_entries_per_user', - type: 'long', - }, - { - name: 'max_subscribers', - type: 'long', - }, - { - name: 'max_fragments_pending_reassembly', - type: 'long', - }, - { - name: 'address_pool_high_threshold', - type: 'long', - }, - { - name: 'address_pool_low_threshold', - type: 'long', - }, - { - name: 'address_port_mapping_high_threshold', - type: 'long', - }, - { - name: 'address_port_mapping_low_threshold', - type: 'long', - }, - { - name: 'address_port_mapping_per_user_high_threshold', - type: 'long', - }, - { - name: 'global_address_mapping_high_threshold', - type: 'long', - }, - { - name: 'vpn_identifier', - type: 'short', - }, - ], - }, - ], - }, - { - key: 's3', - title: 's3', - description: 'S3 fields from s3 input.\n', - release: 'beta', - fields: [ - { - name: 'bucket_name', - type: 'keyword', - description: 'Name of the S3 bucket that this log retrieved from.\n', - }, - { - name: 'object_key', - type: 'keyword', - description: 'Name of the S3 object that this log retrieved from.\n', - }, - ], - }, - { - key: 'cef', - title: 'Decode CEF processor fields', - description: 'Common Event Format (CEF) data.\n', - fields: [ - { - name: 'cef', - type: 'group', - description: - 'By default the `decode_cef` processor writes all data from the CEF message to this `cef` object. It contains the CEF header fields and the extension data.\n', - fields: [ - { - name: 'version', - type: 'keyword', - description: 'Version of the CEF specification used by the message.\n', - }, - { - name: 'device.vendor', - type: 'keyword', - description: 'Vendor of the device that produced the message.\n', - }, - { - name: 'device.product', - type: 'keyword', - description: 'Product of the device that produced the message.\n', - }, - { - name: 'device.version', - type: 'keyword', - description: 'Version of the product that produced the message.\n', - }, - { - name: 'device.event_class_id', - type: 'keyword', - description: 'Unique identifier of the event type.\n', - }, - { - name: 'severity', - type: 'keyword', - example: 'Very-High', - description: - 'Importance of the event. The valid string values are Unknown, Low, Medium, High, and Very-High. The valid integer values are 0-3=Low, 4-6=Medium, 7- 8=High, and 9-10=Very-High.\n', - }, - { - name: 'name', - type: 'keyword', - description: 'Short description of the event.\n', - }, - { - name: 'extensions', - type: 'group', - description: 'Collection of key-value pairs carried in the CEF extension field.\n', - default_field: false, - fields: [ - { - name: 'agentAddress', - type: 'ip', - description: 'The IP address of the ArcSight connector that processed the event.', - }, - { - name: 'agentDnsDomain', - type: 'keyword', - description: - 'The DNS domain name of the ArcSight connector that processed the event.', - }, - { - name: 'agentHostName', - type: 'keyword', - description: 'The hostname of the ArcSight connector that processed the event.', - }, - { - name: 'agentId', - type: 'keyword', - description: 'The agent ID of the ArcSight connector that processed the event.', - }, - { - name: 'agentMacAddress', - type: 'keyword', - description: 'The MAC address of the ArcSight connector that processed the event.', - }, - { - name: 'agentNtDomain', - type: 'keyword', - description: '', - }, - { - name: 'agentReceiptTime', - type: 'date', - description: - 'The time at which information about the event was received by the ArcSight connector.', - }, - { - name: 'agentTimeZone', - type: 'keyword', - description: - 'The agent time zone of the ArcSight connector that processed the event.', - }, - { - name: 'agentTranslatedAddress', - type: 'ip', - description: '', - }, - { - name: 'agentTranslatedZoneExternalID', - type: 'keyword', - description: '', - }, - { - name: 'agentTranslatedZoneURI', - type: 'keyword', - description: '', - }, - { - name: 'agentType', - type: 'keyword', - description: 'The agent type of the ArcSight connector that processed the event', - }, - { - name: 'agentVersion', - type: 'keyword', - description: 'The version of the ArcSight connector that processed the event.', - }, - { - name: 'agentZoneExternalID', - type: 'keyword', - description: '', - }, - { - name: 'agentZoneURI', - type: 'keyword', - description: '', - }, - { - name: 'applicationProtocol', - type: 'keyword', - description: - 'Application level protocol, example values are HTTP, HTTPS, SSHv2, Telnet, POP, IMPA, IMAPS, and so on.', - }, - { - name: 'baseEventCount', - type: 'long', - description: - 'A count associated with this event. How many times was this same event observed? Count can be omitted if it is 1.', - }, - { - name: 'bytesIn', - type: 'long', - description: - 'Number of bytes transferred inbound, relative to the source to destination relationship, meaning that data was flowing from source to destination.', - }, - { - name: 'bytesOut', - type: 'long', - description: - 'Number of bytes transferred outbound relative to the source to destination relationship. For example, the byte number of data flowing from the destination to the source.', - }, - { - name: 'customerExternalID', - type: 'keyword', - description: '', - }, - { - name: 'customerURI', - type: 'keyword', - description: '', - }, - { - name: 'destinationAddress', - type: 'ip', - description: - 'Identifies the destination address that the event refers to in an IP network. The format is an IPv4 address.', - }, - { - name: 'destinationDnsDomain', - type: 'keyword', - description: - 'The DNS domain part of the complete fully qualified domain name (FQDN).', - }, - { - name: 'destinationGeoLatitude', - type: 'double', - description: - "The latitudinal value from which the destination's IP address belongs.", - }, - { - name: 'destinationGeoLongitude', - type: 'double', - description: - "The longitudinal value from which the destination's IP address belongs.", - }, - { - name: 'destinationHostName', - type: 'keyword', - description: - 'Identifies the destination that an event refers to in an IP network. The format should be a fully qualified domain name (FQDN) associated with the destination node, when a node is available.', - }, - { - name: 'destinationMacAddress', - type: 'keyword', - description: 'Six colon-seperated hexadecimal numbers.', - }, - { - name: 'destinationNtDomain', - type: 'keyword', - description: 'The Windows domain name of the destination address.', - }, - { - name: 'destinationPort', - type: 'long', - description: 'The valid port numbers are between 0 and 65535.', - }, - { - name: 'destinationProcessId', - type: 'long', - description: - 'Provides the ID of the destination process associated with the event. For example, if an event contains process ID 105, "105" is the process ID.', - }, - { - name: 'destinationProcessName', - type: 'keyword', - description: "The name of the event's destination process.", - }, - { - name: 'destinationServiceName', - type: 'keyword', - description: 'The service targeted by this event.', - }, - { - name: 'destinationTranslatedAddress', - type: 'ip', - description: - 'Identifies the translated destination that the event refers to in an IP network.', - }, - { - name: 'destinationTranslatedPort', - type: 'long', - description: - 'Port after it was translated; for example, a firewall. Valid port numbers are 0 to 65535.', - }, - { - name: 'destinationTranslatedZoneExternalID', - type: 'keyword', - description: '', - }, - { - name: 'destinationTranslatedZoneURI', - type: 'keyword', - description: - 'The URI for the Translated Zone that the destination asset has been assigned to in ArcSight.', - }, - { - name: 'destinationUserId', - type: 'keyword', - description: - 'Identifies the destination user by ID. For example, in UNIX, the root user is generally associated with user ID 0.', - }, - { - name: 'destinationUserName', - type: 'keyword', - description: - "Identifies the destination user by name. This is the user associated with the event's destination. Email addresses are often mapped into the UserName fields. The recipient is a candidate to put into this field.", - }, - { - name: 'destinationUserPrivileges', - type: 'keyword', - description: - 'The typical values are "Administrator", "User", and "Guest". This identifies the destination user\'s privileges. In UNIX, for example, activity executed on the root user would be identified with destinationUser Privileges of "Administrator".', - }, - { - name: 'destinationZoneExternalID', - type: 'keyword', - description: '', - }, - { - name: 'destinationZoneURI', - type: 'keyword', - description: - 'The URI for the Zone that the destination asset has been assigned to in ArcSight.', - }, - { - name: 'deviceAction', - type: 'keyword', - description: 'Action taken by the device.', - }, - { - name: 'deviceAddress', - type: 'ip', - description: - 'Identifies the device address that an event refers to in an IP network.', - }, - { - name: 'deviceCustomFloatingPoint1Label', - type: 'keyword', - description: - 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', - }, - { - name: 'deviceCustomFloatingPoint3Label', - type: 'keyword', - description: - 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', - }, - { - name: 'deviceCustomFloatingPoint4Label', - type: 'keyword', - description: - 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', - }, - { - name: 'deviceCustomDate1', - type: 'date', - description: - 'One of two timestamp fields available to map fields that do not apply to any other in this dictionary.', - }, - { - name: 'deviceCustomDate1Label', - type: 'keyword', - description: - 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', - }, - { - name: 'deviceCustomDate2', - type: 'date', - description: - 'One of two timestamp fields available to map fields that do not apply to any other in this dictionary.', - }, - { - name: 'deviceCustomDate2Label', - type: 'keyword', - description: - 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', - }, - { - name: 'deviceCustomFloatingPoint1', - type: 'double', - description: - 'One of four floating point fields available to map fields that do not apply to any other in this dictionary.', - }, - { - name: 'deviceCustomFloatingPoint2', - type: 'double', - description: - 'One of four floating point fields available to map fields that do not apply to any other in this dictionary.', - }, - { - name: 'deviceCustomFloatingPoint2Label', - type: 'keyword', - description: - 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', - }, - { - name: 'deviceCustomFloatingPoint3', - type: 'double', - description: - 'One of four floating point fields available to map fields that do not apply to any other in this dictionary.', - }, - { - name: 'deviceCustomFloatingPoint4', - type: 'double', - description: - 'One of four floating point fields available to map fields that do not apply to any other in this dictionary.', - }, - { - name: 'deviceCustomIPv6Address1', - type: 'ip', - description: - 'One of four IPv6 address fields available to map fields that do not apply to any other in this dictionary.', - }, - { - name: 'deviceCustomIPv6Address1Label', - type: 'keyword', - description: - 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', - }, - { - name: 'deviceCustomIPv6Address2', - type: 'ip', - description: - 'One of four IPv6 address fields available to map fields that do not apply to any other in this dictionary.', - }, - { - name: 'deviceCustomIPv6Address2Label', - type: 'keyword', - description: - 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', - }, - { - name: 'deviceCustomIPv6Address3', - type: 'ip', - description: - 'One of four IPv6 address fields available to map fields that do not apply to any other in this dictionary.', - }, - { - name: 'deviceCustomIPv6Address3Label', - type: 'keyword', - description: - 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', - }, - { - name: 'deviceCustomIPv6Address4', - type: 'ip', - description: - 'One of four IPv6 address fields available to map fields that do not apply to any other in this dictionary.', - }, - { - name: 'deviceCustomIPv6Address4Label', - type: 'keyword', - description: - 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', - }, - { - name: 'deviceCustomNumber1', - type: 'long', - description: - 'One of three number fields available to map fields that do not apply to any other in this dictionary. Use sparingly and seek a more specific, dictionary supplied field when possible.', - }, - { - name: 'deviceCustomNumber1Label', - type: 'keyword', - description: - 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', - }, - { - name: 'deviceCustomNumber2', - type: 'long', - description: - 'One of three number fields available to map fields that do not apply to any other in this dictionary. Use sparingly and seek a more specific, dictionary supplied field when possible.', - }, - { - name: 'deviceCustomNumber2Label', - type: 'keyword', - description: - 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', - }, - { - name: 'deviceCustomNumber3', - type: 'long', - description: - 'One of three number fields available to map fields that do not apply to any other in this dictionary. Use sparingly and seek a more specific, dictionary supplied field when possible.', - }, - { - name: 'deviceCustomNumber3Label', - type: 'keyword', - description: - 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', - }, - { - name: 'deviceCustomString1', - type: 'keyword', - description: - 'One of six strings available to map fields that do not apply to any other in this dictionary. Use sparingly and seek a more specific, dictionary supplied field when possible.', - }, - { - name: 'deviceCustomString1Label', - type: 'keyword', - description: - 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', - }, - { - name: 'deviceCustomString2', - type: 'keyword', - description: - 'One of six strings available to map fields that do not apply to any other in this dictionary. Use sparingly and seek a more specific, dictionary supplied field when possible.', - }, - { - name: 'deviceCustomString2Label', - type: 'keyword', - description: - 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', - }, - { - name: 'deviceCustomString3', - type: 'keyword', - description: - 'One of six strings available to map fields that do not apply to any other in this dictionary. Use sparingly and seek a more specific, dictionary supplied field when possible.', - }, - { - name: 'deviceCustomString3Label', - type: 'keyword', - description: - 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', - }, - { - name: 'deviceCustomString4', - type: 'keyword', - description: - 'One of six strings available to map fields that do not apply to any other in this dictionary. Use sparingly and seek a more specific, dictionary supplied field when possible.', - }, - { - name: 'deviceCustomString4Label', - type: 'keyword', - description: - 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', - }, - { - name: 'deviceCustomString5', - type: 'keyword', - description: - 'One of six strings available to map fields that do not apply to any other in this dictionary. Use sparingly and seek a more specific, dictionary supplied field when possible.', - }, - { - name: 'deviceCustomString5Label', - type: 'keyword', - description: - 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', - }, - { - name: 'deviceCustomString6', - type: 'keyword', - description: - 'One of six strings available to map fields that do not apply to any other in this dictionary. Use sparingly and seek a more specific, dictionary supplied field when possible.', - }, - { - name: 'deviceCustomString6Label', - type: 'keyword', - description: - 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', - }, - { - name: 'deviceDirection', - type: 'long', - description: - 'Any information about what direction the observed communication has taken. The following values are supported - "0" for inbound or "1" for outbound.', - }, - { - name: 'deviceDnsDomain', - type: 'keyword', - description: - 'The DNS domain part of the complete fully qualified domain name (FQDN).', - }, - { - name: 'deviceEventCategory', - type: 'keyword', - description: - 'Represents the category assigned by the originating device. Devices often use their own categorization schema to classify event. Example "/Monitor/Disk/Read".', - }, - { - name: 'deviceExternalId', - type: 'keyword', - description: 'A name that uniquely identifies the device generating this event.', - }, - { - name: 'deviceFacility', - type: 'keyword', - description: - 'The facility generating this event. For example, Syslog has an explicit facility associated with every event.', - }, - { - name: 'deviceFlexNumber1', - type: 'long', - description: - 'One of two alternative number fields available to map fields that do not apply to any other in this dictionary. Use sparingly and seek a more specific, dictionary supplied field when possible.', - }, - { - name: 'deviceFlexNumber1Label', - type: 'keyword', - description: - 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', - }, - { - name: 'deviceFlexNumber2', - type: 'long', - description: - 'One of two alternative number fields available to map fields that do not apply to any other in this dictionary. Use sparingly and seek a more specific, dictionary supplied field when possible.', - }, - { - name: 'deviceFlexNumber2Label', - type: 'keyword', - description: - 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', - }, - { - name: 'deviceHostName', - type: 'keyword', - description: - 'The format should be a fully qualified domain name (FQDN) associated with the device node, when a node is available.', - }, - { - name: 'deviceInboundInterface', - type: 'keyword', - description: 'Interface on which the packet or data entered the device.', - }, - { - name: 'deviceMacAddress', - type: 'keyword', - description: 'Six colon-separated hexadecimal numbers.', - }, - { - name: 'deviceNtDomain', - type: 'keyword', - description: 'The Windows domain name of the device address.', - }, - { - name: 'deviceOutboundInterface', - type: 'keyword', - description: 'Interface on which the packet or data left the device.', - }, - { - name: 'devicePayloadId', - type: 'keyword', - description: 'Unique identifier for the payload associated with the event.', - }, - { - name: 'deviceProcessId', - type: 'long', - description: 'Provides the ID of the process on the device generating the event.', - }, - { - name: 'deviceProcessName', - type: 'keyword', - description: - 'Process name associated with the event. An example might be the process generating the syslog entry in UNIX.', - }, - { - name: 'deviceReceiptTime', - type: 'date', - description: - 'The time at which the event related to the activity was received. The format is MMM dd yyyy HH:mm:ss or milliseconds since epoch (Jan 1st 1970)', - }, - { - name: 'deviceTimeZone', - type: 'keyword', - description: 'The timezone for the device generating the event.', - }, - { - name: 'deviceTranslatedAddress', - type: 'ip', - description: - 'Identifies the translated device address that the event refers to in an IP network.', - }, - { - name: 'deviceTranslatedZoneExternalID', - type: 'keyword', - description: '', - }, - { - name: 'deviceTranslatedZoneURI', - type: 'keyword', - description: - 'The URI for the Translated Zone that the device asset has been assigned to in ArcSight.', - }, - { - name: 'deviceZoneExternalID', - type: 'keyword', - description: '', - }, - { - name: 'deviceZoneURI', - type: 'keyword', - description: - 'Thee URI for the Zone that the device asset has been assigned to in ArcSight.', - }, - { - name: 'endTime', - type: 'date', - description: - 'The time at which the activity related to the event ended. The format is MMM dd yyyy HH:mm:ss or milliseconds since epoch (Jan 1st1970). An example would be reporting the end of a session.', - }, - { - name: 'eventId', - type: 'long', - description: 'This is a unique ID that ArcSight assigns to each event.', - }, - { - name: 'eventOutcome', - type: 'keyword', - description: "Displays the outcome, usually as 'success' or 'failure'.", - }, - { - name: 'externalId', - type: 'keyword', - description: - 'The ID used by an originating device. They are usually increasing numbers, associated with events.', - }, - { - name: 'fileCreateTime', - type: 'date', - description: 'Time when the file was created.', - }, - { - name: 'fileHash', - type: 'keyword', - description: 'Hash of a file.', - }, - { - name: 'fileId', - type: 'keyword', - description: 'An ID associated with a file could be the inode.', - }, - { - name: 'fileModificationTime', - type: 'date', - description: 'Time when the file was last modified.', - }, - { - name: 'filename', - type: 'keyword', - description: 'Name of the file only (without its path).', - }, - { - name: 'filePath', - type: 'keyword', - description: 'Full path to the file, including file name itself.', - }, - { - name: 'filePermission', - type: 'keyword', - description: 'Permissions of the file.', - }, - { - name: 'fileSize', - type: 'long', - description: 'Size of the file.', - }, - { - name: 'fileType', - type: 'keyword', - description: 'Type of file (pipe, socket, etc.)', - }, - { - name: 'flexDate1', - type: 'date', - description: - 'A timestamp field available to map a timestamp that does not apply to any other defined timestamp field in this dictionary. Use all flex fields sparingly and seek a more specific, dictionary supplied field when possible. These fields are typically reserved for customer use and should not be set by vendors unless necessary.', - }, - { - name: 'flexDate1Label', - type: 'keyword', - description: - 'The label field is a string and describes the purpose of the flex field.', - }, - { - name: 'flexString1', - type: 'keyword', - description: - 'One of four floating point fields available to map fields that do not apply to any other in this dictionary. Use sparingly and seek a more specific, dictionary supplied field when possible. These fields are typically reserved for customer use and should not be set by vendors unless necessary.', - }, - { - name: 'flexString2', - type: 'keyword', - description: - 'One of four floating point fields available to map fields that do not apply to any other in this dictionary. Use sparingly and seek a more specific, dictionary supplied field when possible. These fields are typically reserved for customer use and should not be set by vendors unless necessary.', - }, - { - name: 'flexString1Label', - type: 'keyword', - description: - 'The label field is a string and describes the purpose of the flex field.', - }, - { - name: 'flexString2Label', - type: 'keyword', - description: - 'The label field is a string and describes the purpose of the flex field.', - }, - { - name: 'message', - type: 'keyword', - description: - 'An arbitrary message giving more details about the event. Multi-line entries can be produced by using \\n as the new line separator.', - }, - { - name: 'oldFileCreateTime', - type: 'date', - description: 'Time when old file was created.', - }, - { - name: 'oldFileHash', - type: 'keyword', - description: 'Hash of the old file.', - }, - { - name: 'oldFileId', - type: 'keyword', - description: 'An ID associated with the old file could be the inode.', - }, - { - name: 'oldFileModificationTime', - type: 'date', - description: 'Time when old file was last modified.', - }, - { - name: 'oldFileName', - type: 'keyword', - description: 'Name of the old file.', - }, - { - name: 'oldFilePath', - type: 'keyword', - description: 'Full path to the old file, including the file name itself.', - }, - { - name: 'oldFilePermission', - type: 'keyword', - description: 'Permissions of the old file.', - }, - { - name: 'oldFileSize', - type: 'long', - description: 'Size of the old file.', - }, - { - name: 'oldFileType', - type: 'keyword', - description: 'Type of the old file (pipe, socket, etc.)', - }, - { - name: 'rawEvent', - type: 'keyword', - description: '', - }, - { - name: 'Reason', - type: 'keyword', - description: - 'The reason an audit event was generated. For example "bad password" or "unknown user". This could also be an error or return code. Example "0x1234".', - }, - { - name: 'requestClientApplication', - type: 'keyword', - description: 'The User-Agent associated with the request.', - }, - { - name: 'requestContext', - type: 'keyword', - description: - 'Description of the content from which the request originated (for example, HTTP Referrer)', - }, - { - name: 'requestCookies', - type: 'keyword', - description: 'Cookies associated with the request.', - }, - { - name: 'requestMethod', - type: 'keyword', - description: 'The HTTP method used to access a URL.', - }, - { - name: 'requestUrl', - type: 'keyword', - description: - 'In the case of an HTTP request, this field contains the URL accessed. The URL should contain the protocol as well.', - }, - { - name: 'sourceAddress', - type: 'ip', - description: 'Identifies the source that an event refers to in an IP network.', - }, - { - name: 'sourceDnsDomain', - type: 'keyword', - description: - 'The DNS domain part of the complete fully qualified domain name (FQDN).', - }, - { - name: 'sourceGeoLatitude', - type: 'double', - description: '', - }, - { - name: 'sourceGeoLongitude', - type: 'double', - description: '', - }, - { - name: 'sourceHostName', - type: 'keyword', - description: - "Identifies the source that an event refers to in an IP network. The format should be a fully qualified domain name (FQDN) associated with the source node, when a mode is available. Examples: 'host' or 'host.domain.com'.\n", - }, - { - name: 'sourceMacAddress', - type: 'keyword', - example: '00:0d:60:af:1b:61', - description: 'Six colon-separated hexadecimal numbers.', - }, - { - name: 'sourceNtDomain', - type: 'keyword', - description: 'The Windows domain name for the source address.', - }, - { - name: 'sourcePort', - type: 'long', - description: 'The valid port numbers are 0 to 65535.', - }, - { - name: 'sourceProcessId', - type: 'long', - description: 'The ID of the source process associated with the event.', - }, - { - name: 'sourceProcessName', - type: 'keyword', - description: "The name of the event's source process.", - }, - { - name: 'sourceServiceName', - type: 'keyword', - description: 'The service that is responsible for generating this event.', - }, - { - name: 'sourceTranslatedAddress', - type: 'ip', - description: - 'Identifies the translated source that the event refers to in an IP network.', - }, - { - name: 'sourceTranslatedPort', - type: 'long', - description: - 'A port number after being translated by, for example, a firewall. Valid port numbers are 0 to 65535.', - }, - { - name: 'sourceTranslatedZoneExternalID', - type: 'keyword', - description: '', - }, - { - name: 'sourceTranslatedZoneURI', - type: 'keyword', - description: - 'The URI for the Translated Zone that the destination asset has been assigned to in ArcSight.', - }, - { - name: 'sourceUserId', - type: 'keyword', - description: - 'Identifies the source user by ID. This is the user associated with the source of the event. For example, in UNIX, the root user is generally associated with user ID 0.', - }, - { - name: 'sourceUserName', - type: 'keyword', - description: - 'Identifies the source user by name. Email addresses are also mapped into the UserName fields. The sender is a candidate to put into this field.', - }, - { - name: 'sourceUserPrivileges', - type: 'keyword', - description: - 'The typical values are "Administrator", "User", and "Guest". It identifies the source user\'s privileges. In UNIX, for example, activity executed by the root user would be identified with "Administrator".', - }, - { - name: 'sourceZoneExternalID', - type: 'keyword', - description: '', - }, - { - name: 'sourceZoneURI', - type: 'keyword', - description: - 'The URI for the Zone that the source asset has been assigned to in ArcSight.', - }, - { - name: 'startTime', - type: 'date', - description: - 'The time when the activity the event referred to started. The format is MMM dd yyyy HH:mm:ss or milliseconds since epoch (Jan 1st 1970)', - }, - { - name: 'transportProtocol', - type: 'keyword', - description: - 'Identifies the Layer-4 protocol used. The possible values are protocols such as TCP or UDP.', - }, - { - name: 'type', - type: 'long', - description: - '0 means base event, 1 means aggregated, 2 means correlation, and 3 means action. This field can be omitted for base events (type 0).', - }, - { - name: 'categoryDeviceType', - type: 'keyword', - description: 'Device type. Examples - Proxy, IDS, Web Server', - }, - { - name: 'categoryObject', - type: 'keyword', - description: - 'Object that the event is about. For example it can be an operating sytem, database, file, etc.', - }, - { - name: 'categoryBehavior', - type: 'keyword', - description: - "Action or a behavior associated with an event. It's what is being done to the object.", - }, - { - name: 'categoryTechnique', - type: 'keyword', - description: 'Technique being used (e.g. /DoS).', - }, - { - name: 'categoryDeviceGroup', - type: 'keyword', - description: 'General device group like Firewall.', - }, - { - name: 'categorySignificance', - type: 'keyword', - description: 'Characterization of the importance of the event.', - }, - { - name: 'categoryOutcome', - type: 'keyword', - description: 'Outcome of the event (e.g. sucess, failure, or attempt).', - }, - { - name: 'managerReceiptTime', - type: 'date', - description: 'When the Arcsight ESM received the event.', - }, - ], - }, - ], - }, - { - name: 'source.service.name', - type: 'keyword', - description: 'Service that is the source of the event.', - }, - { - name: 'destination.service.name', - type: 'keyword', - description: 'Service that is the target of the event.', - }, - ], - }, -]; diff --git a/x-pack/plugins/security_solution/server/utils/beat_schema/8.0.0/index.ts b/x-pack/plugins/security_solution/server/utils/beat_schema/8.0.0/index.ts deleted file mode 100644 index bd7e7d4eec83b..0000000000000 --- a/x-pack/plugins/security_solution/server/utils/beat_schema/8.0.0/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { auditbeatSchema } from './auditbeat'; -export { filebeatSchema } from './filebeat'; -export { packetbeatSchema } from './packetbeat'; -export { winlogbeatSchema } from './winlogbeat'; -export { ecsSchema } from './ecs'; - -export const extraSchemaField = { - _id: { - description: 'Each document has an _id that uniquely identifies it', - example: 'Y-6TfmcB0WOhS6qyMv3s', - footnote: '', - group: 1, - level: 'core', - name: '_id', - required: true, - type: 'keyword', - }, - _index: { - description: - 'An index is like a ‘database’ in a relational database. It has a mapping which defines multiple types. An index is a logical namespace which maps to one or more primary shards and can have zero or more replica shards.', - example: 'auditbeat-8.0.0-2019.02.19-000001', - footnote: '', - group: 1, - level: 'core', - name: '_index', - required: true, - type: 'keyword', - }, -}; - -export const baseCategoryFields = ['@timestamp', 'labels', 'message', 'tags']; diff --git a/x-pack/plugins/security_solution/server/utils/beat_schema/8.0.0/packetbeat.ts b/x-pack/plugins/security_solution/server/utils/beat_schema/8.0.0/packetbeat.ts deleted file mode 100644 index 0be2e48fe4668..0000000000000 --- a/x-pack/plugins/security_solution/server/utils/beat_schema/8.0.0/packetbeat.ts +++ /dev/null @@ -1,8556 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -/** - * An instance of the unmodified schema exported from auditbeat-8.0.0-SNAPSHOT-darwin-x86_64.tar.gz - * - */ - -import { Schema } from '../type'; - -export const packetbeatSchema: Schema = [ - { - key: 'ecs', - title: 'ECS', - description: 'ECS Fields.', - fields: [ - { - name: '@timestamp', - level: 'core', - required: true, - type: 'date', - description: - 'Date/time when the event originated.\n\nThis is the date/time extracted from the event, typically representing when\nthe event was generated by the source.\n\nIf the event source has no original timestamp, this value is typically populated\nby the first time the event was received by the pipeline.\n\nRequired field for all events.', - example: '2016-05-23T08:05:34.853Z', - }, - { - name: 'labels', - level: 'core', - type: 'object', - object_type: 'keyword', - description: - 'Custom key/value pairs.\n\nCan be used to add meta information to events. Should not contain nested objects.\nAll values are stored as keyword.\n\nExample: `docker` and `k8s` labels.', - example: '{"application": "foo-bar", "env": "production"}', - }, - { - name: 'message', - level: 'core', - type: 'text', - description: - 'For log events the message field contains the log message, optimized\nfor viewing in a log viewer.\n\nFor structured logs without an original message field, other fields can be concatenated\nto form a human-readable summary of the event.\n\nIf multiple messages exist, they can be combined into one message.', - example: 'Hello World', - }, - { - name: 'tags', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'List of keywords used to tag each event.', - example: '["production", "env2"]', - }, - { - name: 'agent', - title: 'Agent', - group: 2, - description: - 'The agent fields contain the data about the software entity, if\nany, that collects, detects, or observes events on a host, or takes measurements\non a host.\n\nExamples include Beats. Agents may also run on observers. ECS agent.* fields\nshall be populated with details of the agent running on the host or observer\nwhere the event happened or the measurement was taken.', - footnote: - 'Examples: In the case of Beats for logs, the agent.name is filebeat.\nFor APM, it is the agent running in the app/service. The agent information does\nnot change if data is sent through queuing systems like Kafka, Redis, or processing\nsystems such as Logstash or APM Server.', - type: 'group', - fields: [ - { - name: 'ephemeral_id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Ephemeral identifier of this agent (if one exists).\n\nThis id normally changes across restarts, but `agent.id` does not.', - example: '8a4f500f', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique identifier of this agent (if one exists).\n\nExample: For Beats this would be beat.id.', - example: '8a4f500d', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Custom name of the agent.\n\nThis is a name that can be given to an agent. This can be helpful if for example\ntwo Filebeat instances are running on the same host but a human readable separation\nis needed on which Filebeat instance data is coming from.\n\nIf no name is given, the name is often left empty.', - example: 'foo', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Type of the agent.\n\nThe agent type stays always the same and should be given by the agent used.\nIn case of Filebeat the agent would always be Filebeat also if two Filebeat\ninstances are run on the same machine.', - example: 'filebeat', - }, - { - name: 'version', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Version of the agent.', - example: '6.0.0-rc2', - }, - ], - }, - { - name: 'as', - title: 'Autonomous System', - group: 2, - description: - 'An autonomous system (AS) is a collection of connected Internet Protocol\n(IP) routing prefixes under the control of one or more network operators on\nbehalf of a single administrative entity or domain that presents a common, clearly\ndefined routing policy to the internet.', - type: 'group', - fields: [ - { - name: 'number', - level: 'extended', - type: 'long', - description: - 'Unique number allocated to the autonomous system. The autonomous\nsystem number (ASN) uniquely identifies each network on the Internet.', - example: 15169, - }, - { - name: 'organization.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Organization name.', - example: 'Google LLC', - }, - ], - }, - { - name: 'client', - title: 'Client', - group: 2, - description: - 'A client is defined as the initiator of a network connection for\nevents regarding sessions, connections, or bidirectional flow records.\n\nFor TCP events, the client is the initiator of the TCP connection that sends\nthe SYN packet(s). For other protocols, the client is generally the initiator\nor requestor in the network transaction. Some systems use the term "originator"\nto refer the client in TCP connections. The client fields describe details about\nthe system acting as the client in the network event. Client fields are usually\npopulated in conjunction with server fields. Client fields are generally not\npopulated for packet-level events.\n\nClient / server representations can add semantic context to an exchange, which\nis helpful to visualize the data in certain situations. If your context falls\nin that category, you should still ensure that source and destination are filled\nappropriately.', - type: 'group', - fields: [ - { - name: 'address', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Some event client addresses are defined ambiguously. The event\nwill sometimes list an IP, a domain or a unix socket. You should always store\nthe raw address in the `.address` field.\n\nThen it should be duplicated to `.ip` or `.domain`, depending on which one\nit is.', - }, - { - name: 'as.number', - level: 'extended', - type: 'long', - description: - 'Unique number allocated to the autonomous system. The autonomous\nsystem number (ASN) uniquely identifies each network on the Internet.', - example: 15169, - }, - { - name: 'as.organization.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Organization name.', - example: 'Google LLC', - }, - { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - description: 'Bytes sent from the client to the server.', - example: 184, - }, - { - name: 'domain', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Client domain.', - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: - 'IP address of the client.\n\nCan be one or multiple IPv4 or IPv6 addresses.', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'MAC address of the client.', - }, - { - name: 'nat.ip', - level: 'extended', - type: 'ip', - description: - 'Translated IP of source based NAT sessions (e.g. internal client\nto internet).\n\nTypically connections traversing load balancers, firewalls, or routers.', - }, - { - name: 'nat.port', - level: 'extended', - type: 'long', - format: 'string', - description: - 'Translated port of source based NAT sessions (e.g. internal client\nto internet).\n\nTypically connections traversing load balancers, firewalls, or routers.', - }, - { - name: 'packets', - level: 'core', - type: 'long', - description: 'Packets sent from the client to the server.', - example: 12, - }, - { - name: 'port', - level: 'core', - type: 'long', - format: 'string', - description: 'Port of the client.', - }, - { - name: 'registered_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The highest registered client domain, stripped of the subdomain.\n\nFor example, the registered domain for "foo.google.com" is "google.com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last two labels will not work well for TLDs such as "co.uk".', - example: 'google.com', - }, - { - name: 'top_level_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The effective top level domain (eTLD), also known as the domain\nsuffix, is the last part of the domain name. For example, the top level domain\nfor google.com is "com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last label will not work well for effective TLDs such as "co.uk".', - example: 'co.uk', - }, - { - name: 'user.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the user is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.email', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'User email address.', - }, - { - name: 'user.full_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'user.group.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.group.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'user.group.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - { - name: 'user.hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'user.id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifiers of the user.', - }, - { - name: 'user.name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'cloud', - title: 'Cloud', - group: 2, - description: 'Fields related to the cloud or infrastructure the events are coming\nfrom.', - footnote: - 'Examples: If Metricbeat is running on an EC2 host and fetches data\nfrom its host, the cloud info contains the data about this machine. If Metricbeat\nruns on a remote machine outside the cloud and fetches data from a service running\nin the cloud, the field contains cloud data from the machine the service is\nrunning on.', - type: 'group', - fields: [ - { - name: 'account.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The cloud account or organization id used to identify different\nentities in a multi-tenant environment.\n\nExamples: AWS account id, Google Cloud ORG Id, or other unique identifier.', - example: 666777888999, - }, - { - name: 'availability_zone', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Availability zone in which this host is running.', - example: 'us-east-1c', - }, - { - name: 'instance.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Instance ID of the host machine.', - example: 'i-1234567890abcdef0', - }, - { - name: 'instance.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Instance name of the host machine.', - }, - { - name: 'machine.type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Machine type of the host machine.', - example: 't2.medium', - }, - { - name: 'provider', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the cloud provider. Example values are aws, azure, gcp,\nor digitalocean.', - example: 'aws', - }, - { - name: 'region', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Region in which this host is running.', - example: 'us-east-1', - }, - ], - }, - { - name: 'code_signature', - title: 'Code Signature', - group: 2, - description: 'These fields contain information about binary code signatures.', - type: 'group', - fields: [ - { - name: 'exists', - level: 'core', - type: 'boolean', - description: 'Boolean to capture if a signature is present.', - example: 'true', - default_field: false, - }, - { - name: 'status', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Additional information about the certificate status.\n\nThis is useful for logging cryptographic errors with the certificate validity\nor trust status. Leave unpopulated if the validity or trust of the certificate\nwas unchecked.', - example: 'ERROR_UNTRUSTED_ROOT', - default_field: false, - }, - { - name: 'subject_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Subject name of the code signer', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'trusted', - level: 'extended', - type: 'boolean', - description: - 'Stores the trust status of the certificate chain.\n\nValidating the trust of the certificate chain may be complicated, and this\nfield should only be populated by tools that actively check the status.', - example: 'true', - default_field: false, - }, - { - name: 'valid', - level: 'extended', - type: 'boolean', - description: - 'Boolean to capture if the digital signature is verified against\nthe binary content.\n\nLeave unpopulated if a certificate was unchecked.', - example: 'true', - default_field: false, - }, - ], - }, - { - name: 'container', - title: 'Container', - group: 2, - description: - 'Container fields are used for meta information about the specific\ncontainer that is the source of information.\n\nThese fields help correlate data based containers from any runtime.', - type: 'group', - fields: [ - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique container id.', - }, - { - name: 'image.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the image the container was built on.', - }, - { - name: 'image.tag', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Container image tags.', - }, - { - name: 'labels', - level: 'extended', - type: 'object', - object_type: 'keyword', - description: 'Image labels.', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Container name.', - }, - { - name: 'runtime', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Runtime managing this container.', - example: 'docker', - }, - ], - }, - { - name: 'destination', - title: 'Destination', - group: 2, - description: - 'Destination fields describe details about the destination of a packet/event.\n\nDestination fields are usually populated in conjunction with source fields.', - type: 'group', - fields: [ - { - name: 'address', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Some event destination addresses are defined ambiguously. The\nevent will sometimes list an IP, a domain or a unix socket. You should always\nstore the raw address in the `.address` field.\n\nThen it should be duplicated to `.ip` or `.domain`, depending on which one\nit is.', - }, - { - name: 'as.number', - level: 'extended', - type: 'long', - description: - 'Unique number allocated to the autonomous system. The autonomous\nsystem number (ASN) uniquely identifies each network on the Internet.', - example: 15169, - }, - { - name: 'as.organization.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Organization name.', - example: 'Google LLC', - }, - { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - description: 'Bytes sent from the destination to the source.', - example: 184, - }, - { - name: 'domain', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Destination domain.', - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: - 'IP address of the destination.\n\nCan be one or multiple IPv4 or IPv6 addresses.', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'MAC address of the destination.', - }, - { - name: 'nat.ip', - level: 'extended', - type: 'ip', - description: - 'Translated ip of destination based NAT sessions (e.g. internet\nto private DMZ)\n\nTypically used with load balancers, firewalls, or routers.', - }, - { - name: 'nat.port', - level: 'extended', - type: 'long', - format: 'string', - description: - 'Port the source session is translated to by NAT Device.\n\nTypically used with load balancers, firewalls, or routers.', - }, - { - name: 'packets', - level: 'core', - type: 'long', - description: 'Packets sent from the destination to the source.', - example: 12, - }, - { - name: 'port', - level: 'core', - type: 'long', - format: 'string', - description: 'Port of the destination.', - }, - { - name: 'registered_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The highest registered destination domain, stripped of the subdomain.\n\nFor example, the registered domain for "foo.google.com" is "google.com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last two labels will not work well for TLDs such as "co.uk".', - example: 'google.com', - }, - { - name: 'top_level_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The effective top level domain (eTLD), also known as the domain\nsuffix, is the last part of the domain name. For example, the top level domain\nfor google.com is "com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last label will not work well for effective TLDs such as "co.uk".', - example: 'co.uk', - }, - { - name: 'user.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the user is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.email', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'User email address.', - }, - { - name: 'user.full_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'user.group.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.group.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'user.group.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - { - name: 'user.hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'user.id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifiers of the user.', - }, - { - name: 'user.name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'dll', - title: 'DLL', - group: 2, - description: - 'These fields contain information about code libraries dynamically\nloaded into processes.\n\n\nMany operating systems refer to "shared code libraries" with different names,\nbut this field set refers to all of the following:\n\n* Dynamic-link library (`.dll`) commonly used on Windows\n\n* Shared Object (`.so`) commonly used on Unix-like operating systems\n\n* Dynamic library (`.dylib`) commonly used on macOS', - type: 'group', - fields: [ - { - name: 'code_signature.exists', - level: 'core', - type: 'boolean', - description: 'Boolean to capture if a signature is present.', - example: 'true', - default_field: false, - }, - { - name: 'code_signature.status', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Additional information about the certificate status.\n\nThis is useful for logging cryptographic errors with the certificate validity\nor trust status. Leave unpopulated if the validity or trust of the certificate\nwas unchecked.', - example: 'ERROR_UNTRUSTED_ROOT', - default_field: false, - }, - { - name: 'code_signature.subject_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Subject name of the code signer', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'code_signature.trusted', - level: 'extended', - type: 'boolean', - description: - 'Stores the trust status of the certificate chain.\n\nValidating the trust of the certificate chain may be complicated, and this\nfield should only be populated by tools that actively check the status.', - example: 'true', - default_field: false, - }, - { - name: 'code_signature.valid', - level: 'extended', - type: 'boolean', - description: - 'Boolean to capture if the digital signature is verified against\nthe binary content.\n\nLeave unpopulated if a certificate was unchecked.', - example: 'true', - default_field: false, - }, - { - name: 'hash.md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'MD5 hash.', - default_field: false, - }, - { - name: 'hash.sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA1 hash.', - default_field: false, - }, - { - name: 'hash.sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA256 hash.', - default_field: false, - }, - { - name: 'hash.sha512', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA512 hash.', - default_field: false, - }, - { - name: 'name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the library.\n\nThis generally maps to the name of the file on disk.', - example: 'kernel32.dll', - default_field: false, - }, - { - name: 'path', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Full file path of the library.', - example: 'C:\\Windows\\System32\\kernel32.dll', - default_field: false, - }, - { - name: 'pe.company', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal company name of the file, provided at compile-time.', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'pe.description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal description of the file, provided at compile-time.', - example: 'Paint', - default_field: false, - }, - { - name: 'pe.file_version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal version of the file, provided at compile-time.', - example: '6.3.9600.17415', - default_field: false, - }, - { - name: 'pe.original_file_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal name of the file, provided at compile-time.', - example: 'MSPAINT.EXE', - default_field: false, - }, - { - name: 'pe.product', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal product name of the file, provided at compile-time.', - example: 'Microsoft® Windows® Operating System', - default_field: false, - }, - ], - }, - { - name: 'dns', - title: 'DNS', - group: 2, - description: - 'Fields describing DNS queries and answers.\n\nDNS events should either represent a single DNS query prior to getting answers\n(`dns.type:query`) or they should represent a full exchange and contain the\nquery details as well as all of the answers that were provided for this query\n(`dns.type:answer`).', - type: 'group', - fields: [ - { - name: 'answers', - level: 'extended', - type: 'object', - object_type: 'keyword', - description: - 'An array containing an object for each answer section returned\nby the server.\n\nThe main keys that should be present in these objects are defined by ECS.\nRecords that have more information may contain more keys than what ECS defines.\n\nNot all DNS data sources give all details about DNS answers. At minimum, answer\nobjects must contain the `data` key. If more information is available, map\nas much of it to ECS as possible, and add any additional fields to the answer\nobjects as custom fields.', - }, - { - name: 'answers.class', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The class of DNS data contained in this resource record.', - example: 'IN', - }, - { - name: 'answers.data', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The data describing the resource.\n\nThe meaning of this data depends on the type and class of the resource record.', - example: '10.10.10.10', - }, - { - name: 'answers.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The domain name to which this resource record pertains.\n\nIf a chain of CNAME is being resolved, each answer `name` should be the\none that corresponds with the answer `data`. It should not simply be the\noriginal `question.name` repeated.', - example: 'www.google.com', - }, - { - name: 'answers.ttl', - level: 'extended', - type: 'long', - description: - 'The time interval in seconds that this resource record may be cached\nbefore it should be discarded. Zero values mean that the data should not be\ncached.', - example: 180, - }, - { - name: 'answers.type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The type of data contained in this resource record.', - example: 'CNAME', - }, - { - name: 'header_flags', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Array of 2 letter DNS header flags.\n\nExpected values are: AA, TC, RD, RA, AD, CD, DO.', - example: ['RD', 'RA'], - }, - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The DNS packet identifier assigned by the program that generated\nthe query. The identifier is copied to the response.', - example: 62111, - }, - { - name: 'op_code', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The DNS operation code that specifies the kind of query in the\nmessage. This value is set by the originator of a query and copied into the\nresponse.', - example: 'QUERY', - }, - { - name: 'question.class', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The class of records being queried.', - example: 'IN', - }, - { - name: 'question.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The name being queried.\n\nIf the name field contains non-printable characters (below 32 or above 126),\nthose characters should be represented as escaped base 10 integers (\\DDD).\nBack slashes and quotes should be escaped. Tabs, carriage returns, and line\nfeeds should be converted to \\t, \\r, and \\n respectively.', - example: 'www.google.com', - }, - { - name: 'question.registered_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The highest registered domain, stripped of the subdomain.\n\nFor example, the registered domain for "foo.google.com" is "google.com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last two labels will not work well for TLDs such as "co.uk".', - example: 'google.com', - }, - { - name: 'question.subdomain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The subdomain is all of the labels under the registered_domain.\n\nIf the domain has multiple levels of subdomain, such as "sub2.sub1.example.com",\nthe subdomain field should contain "sub2.sub1", with no trailing period.', - example: 'www', - }, - { - name: 'question.top_level_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The effective top level domain (eTLD), also known as the domain\nsuffix, is the last part of the domain name. For example, the top level domain\nfor google.com is "com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last label will not work well for effective TLDs such as "co.uk".', - example: 'co.uk', - }, - { - name: 'question.type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The type of record being queried.', - example: 'AAAA', - }, - { - name: 'resolved_ip', - level: 'extended', - type: 'ip', - description: - 'Array containing all IPs seen in `answers.data`.\n\nThe `answers` array can be difficult to use, because of the variety of data\nformats it can contain. Extracting all IP addresses seen in there to `dns.resolved_ip`\nmakes it possible to index them as IP addresses, and makes them easier to\nvisualize and query for.', - example: ['10.10.10.10', '10.10.10.11'], - }, - { - name: 'response_code', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The DNS response code.', - example: 'NOERROR', - }, - { - name: 'type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The type of DNS event captured, query or answer.\n\nIf your source of DNS events only gives you DNS queries, you should only create\ndns events of type `dns.type:query`.\n\nIf your source of DNS events gives you answers as well, you should create\none event per query (optionally as soon as the query is seen). And a second\nevent containing all query details as well as an array of answers.', - example: 'answer', - }, - ], - }, - { - name: 'ecs', - title: 'ECS', - group: 2, - description: 'Meta-information specific to ECS.', - type: 'group', - fields: [ - { - name: 'version', - level: 'core', - required: true, - type: 'keyword', - ignore_above: 1024, - description: - 'ECS version this event conforms to. `ecs.version` is a required\nfield and must exist in all events.\n\nWhen querying across multiple indices -- which may conform to slightly different\nECS versions -- this field lets integrations adjust to the schema version\nof the events.', - example: '1.0.0', - }, - ], - }, - { - name: 'error', - title: 'Error', - group: 2, - description: - 'These fields can represent errors of any kind.\n\nUse them for errors that happen while fetching events or in cases where the\nevent itself contains an error.', - type: 'group', - fields: [ - { - name: 'code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Error code describing the error.', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the error.', - }, - { - name: 'message', - level: 'core', - type: 'text', - description: 'Error message.', - }, - { - name: 'stack_trace', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'The stack trace of this error in plain text.', - }, - { - name: 'type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The type of the error, for example the class name of the exception.', - example: 'java.lang.NullPointerException', - }, - ], - }, - { - name: 'event', - title: 'Event', - group: 2, - description: - 'The event fields are used for context information about the log\nor metric event itself.\n\nA log is defined as an event containing details of something that happened.\nLog events must include the time at which the thing happened. Examples of log\nevents include a process starting on a host, a network packet being sent from\na source to a destination, or a network connection between a client and a server\nbeing initiated or closed. A metric is defined as an event containing one or\nmore numerical measurements and the time at which the measurement was taken.\nExamples of metric events include memory pressure measured on a host and device\ntemperature. See the `event.kind` definition in this section for additional\ndetails about metric and state events.', - type: 'group', - fields: [ - { - name: 'action', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'The action captured by the event.\n\nThis describes the information in the event. It is more specific than `event.category`.\nExamples are `group-add`, `process-started`, `file-created`. The value is\nnormally defined by the implementer.', - example: 'user-password-change', - }, - { - name: 'category', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'This is one of four ECS Categorization Fields, and indicates the\nsecond level in the ECS category hierarchy.\n\n`event.category` represents the "big buckets" of ECS categories. For example,\nfiltering on `event.category:process` yields all events relating to process\nactivity. This field is closely related to `event.type`, which is used as\na subcategory.\n\nThis field is an array. This will allow proper categorization of some events\nthat fall in multiple categories.', - example: 'authentication', - }, - { - name: 'code', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Identification code for this event, if one exists.\n\nSome event sources use event codes to identify messages unambiguously, regardless\nof message language or wording adjustments over time. An example of this is\nthe Windows Event ID.', - example: 4648, - }, - { - name: 'created', - level: 'core', - type: 'date', - description: - 'event.created contains the date/time when the event was first\nread by an agent, or by your pipeline.\n\nThis field is distinct from @timestamp in that @timestamp typically contain\nthe time extracted from the original event.\n\nIn most situations, these two timestamps will be slightly different. The difference\ncan be used to calculate the delay between your source generating an event,\nand the time when your agent first processed it. This can be used to monitor\nyour agent or pipeline ability to keep up with your event source.\n\nIn case the two timestamps are identical, @timestamp should be used.', - example: '2016-05-23T08:05:34.857Z', - }, - { - name: 'dataset', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the dataset.\n\nIf an event source publishes more than one type of log or events (e.g. access\nlog, error log), the dataset is used to specify which one the event comes\nfrom.\n\nIt is recommended but not required to start the dataset name with the module\nname, followed by a dot, then the dataset name.', - example: 'apache.access', - }, - { - name: 'duration', - level: 'core', - type: 'long', - format: 'duration', - input_format: 'nanoseconds', - output_format: 'asMilliseconds', - output_precision: 1, - description: - 'Duration of the event in nanoseconds.\n\nIf event.start and event.end are known this value should be the difference\nbetween the end and start time.', - }, - { - name: 'end', - level: 'extended', - type: 'date', - description: - 'event.end contains the date when the event ended or when the activity\nwas last observed.', - }, - { - name: 'hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Hash (perhaps logstash fingerprint) of raw field to be able to\ndemonstrate log integrity.', - example: '123456789012345678901234567890ABCD', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique ID to describe the event.', - example: '8a4f500d', - }, - { - name: 'ingested', - level: 'core', - type: 'date', - description: - 'Timestamp when an event arrived in the central data store.\n\nThis is different from `@timestamp`, which is when the event originally occurred. It is\nalso different from `event.created`, which is meant to capture the first time\nan agent saw the event.\n\nIn normal conditions, assuming no tampering, the timestamps should chronologically\nlook like this: `@timestamp` < `event.created` < `event.ingested`.', - example: '2016-05-23T08:05:35.101Z', - default_field: false, - }, - { - name: 'kind', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'This is one of four ECS Categorization Fields, and indicates the\nhighest level in the ECS category hierarchy.\n\n`event.kind` gives high-level information about what type of information the\nevent contains, without being specific to the contents of the event. For example,\nvalues of this field distinguish alert events from metric events.\n\nThe value of this field can be used to inform how these kinds of events should\nbe handled. They may warrant different retention, different access control,\nit may also help understand whether the data coming in at a regular interval\nor not.', - example: 'alert', - }, - { - name: 'module', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the module this data is coming from.\n\nIf your monitoring agent supports the concept of modules or plugins to process\nevents of a given source (e.g. Apache logs), `event.module` should contain\nthe name of this module.', - example: 'apache', - }, - { - name: 'original', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Raw text message of entire event. Used to demonstrate log integrity.\n\nThis field is not indexed and doc_values are disabled. It cannot be searched,\nbut it can be retrieved from `_source`.', - example: - 'Sep 19 08:26:10 host CEF:0|Security| threatmanager|1.0|100|\nworm successfully stopped|10|src=10.0.0.1 dst=2.1.2.2spt=1232', - }, - { - name: 'outcome', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'This is one of four ECS Categorization Fields, and indicates the\nlowest level in the ECS category hierarchy.\n\n`event.outcome` simply denotes whether the event represents a success or a\nfailure from the perspective of the entity that produced the event.\n\nNote that when a single transaction is described in multiple events, each\nevent may populate different values of `event.outcome`, according to their\nperspective.\n\nAlso note that in the case of a compound event (a single event that contains\nmultiple logical events), this field should be populated with the value that\nbest captures the overall success or failure from the perspective of the event\nproducer.\n\nFurther note that not all events will have an associated outcome. For example,\nthis field is generally not populated for metric events, events with `event.type:info`,\nor any events for which an outcome does not make logical sense.', - example: 'success', - }, - { - name: 'provider', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Source of the event.\n\nEvent transports such as Syslog or the Windows Event Log typically mention\nthe source of an event. It can be the name of the software that generated\nthe event (e.g. Sysmon, httpd), or of a subsystem of the operating system\n(kernel, Microsoft-Windows-Security-Auditing).', - example: 'kernel', - }, - { - name: 'reference', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Reference URL linking to additional information about this event.\n\nThis URL links to a static definition of the this event. Alert events, indicated\nby `event.kind:alert`, are a common use case for this field.', - example: 'https://system.vendor.com/event/#0001234', - default_field: false, - }, - { - name: 'risk_score', - level: 'core', - type: 'float', - description: - "Risk score or priority of the event (e.g. security solutions).\nUse your system's original value here.", - }, - { - name: 'risk_score_norm', - level: 'extended', - type: 'float', - description: - 'Normalized risk score or priority of the event, on a scale of\n0 to 100.\n\nThis is mainly useful if you use more than one system that assigns risk scores,\nand you want to see a normalized value across all systems.', - }, - { - name: 'sequence', - level: 'extended', - type: 'long', - format: 'string', - description: - 'Sequence number of the event.\n\nThe sequence number is a value published by some event sources, to make the\nexact ordering of events unambiguous, regardless of the timestamp precision.', - }, - { - name: 'severity', - level: 'core', - type: 'long', - format: 'string', - description: - 'The numeric severity of the event according to your event source.\n\nWhat the different severity values mean can be different between sources and\nuse cases. It is up to the implementer to make sure severities are consistent\nacross events from the same source.\n\nThe Syslog severity belongs in `log.syslog.severity.code`. `event.severity`\nis meant to represent the severity according to the event source (e.g. firewall,\nIDS). If the event source does not publish its own severity, you may optionally\ncopy the `log.syslog.severity.code` to `event.severity`.', - example: 7, - }, - { - name: 'start', - level: 'extended', - type: 'date', - description: - 'event.start contains the date when the event started or when the\nactivity was first observed.', - }, - { - name: 'timezone', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'This field should be populated when the event timestamp does\nnot include timezone information already (e.g. default Syslog timestamps).\nIt is optional otherwise.\n\nAcceptable timezone formats are: a canonical ID (e.g. "Europe/Amsterdam"),\nabbreviated (e.g. "EST") or an HH:mm differential (e.g. "-05:00").', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'This is one of four ECS Categorization Fields, and indicates the\nthird level in the ECS category hierarchy.\n\n`event.type` represents a categorization "sub-bucket" that, when used along\nwith the `event.category` field values, enables filtering events down to a\nlevel appropriate for single visualization.\n\nThis field is an array. This will allow proper categorization of some events\nthat fall in multiple event types.', - }, - { - name: 'url', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'URL linking to an external system to continue investigation of\nthis event.\n\nThis URL links to another system where in-depth investigation of the specific\noccurence of this event can take place. Alert events, indicated by `event.kind:alert`,\nare a common use case for this field.', - example: 'https://mysystem.mydomain.com/alert/5271dedb-f5b0-4218-87f0-4ac4870a38fe', - default_field: false, - }, - ], - }, - { - name: 'file', - title: 'File', - group: 2, - description: - 'A file is defined as a set of information that has been created\non, or has existed on a filesystem.\n\nFile objects can be associated with host events, network events, and/or file\nevents (e.g., those produced by File Integrity Monitoring [FIM] products or\nservices). File fields provide details about the affected file associated with\nthe event or metric.', - type: 'group', - fields: [ - { - name: 'accessed', - level: 'extended', - type: 'date', - description: - 'Last time the file was accessed.\n\nNote that not all filesystems keep track of access time.', - }, - { - name: 'attributes', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Array of file attributes.\n\nAttributes names will vary by platform. Here is a non-exhaustive list of values\nthat are expected in this field: archive, compressed, directory, encrypted,\nexecute, hidden, read, readonly, system, write.', - example: '["readonly", "system"]', - default_field: false, - }, - { - name: 'code_signature.exists', - level: 'core', - type: 'boolean', - description: 'Boolean to capture if a signature is present.', - example: 'true', - default_field: false, - }, - { - name: 'code_signature.status', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Additional information about the certificate status.\n\nThis is useful for logging cryptographic errors with the certificate validity\nor trust status. Leave unpopulated if the validity or trust of the certificate\nwas unchecked.', - example: 'ERROR_UNTRUSTED_ROOT', - default_field: false, - }, - { - name: 'code_signature.subject_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Subject name of the code signer', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'code_signature.trusted', - level: 'extended', - type: 'boolean', - description: - 'Stores the trust status of the certificate chain.\n\nValidating the trust of the certificate chain may be complicated, and this\nfield should only be populated by tools that actively check the status.', - example: 'true', - default_field: false, - }, - { - name: 'code_signature.valid', - level: 'extended', - type: 'boolean', - description: - 'Boolean to capture if the digital signature is verified against\nthe binary content.\n\nLeave unpopulated if a certificate was unchecked.', - example: 'true', - default_field: false, - }, - { - name: 'created', - level: 'extended', - type: 'date', - description: - 'File creation time.\n\nNote that not all filesystems store the creation time.', - }, - { - name: 'ctime', - level: 'extended', - type: 'date', - description: - 'Last time the file attributes or metadata changed.\n\nNote that changes to the file content will update `mtime`. This implies `ctime`\nwill be adjusted at the same time, since `mtime` is an attribute of the file.', - }, - { - name: 'device', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Device that is the source of the file.', - example: 'sda', - }, - { - name: 'directory', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Directory where the file is located. It should include the drive\nletter, when appropriate.', - example: '/home/alice', - }, - { - name: 'drive_letter', - level: 'extended', - type: 'keyword', - ignore_above: 1, - description: - 'Drive letter where the file is located. This field is only relevant\non Windows.\n\nThe value should be uppercase, and not include the colon.', - example: 'C', - default_field: false, - }, - { - name: 'extension', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'File extension.', - example: 'png', - }, - { - name: 'gid', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Primary group ID (GID) of the file.', - example: '1001', - }, - { - name: 'group', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Primary group name of the file.', - example: 'alice', - }, - { - name: 'hash.md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'MD5 hash.', - }, - { - name: 'hash.sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA1 hash.', - }, - { - name: 'hash.sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA256 hash.', - }, - { - name: 'hash.sha512', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA512 hash.', - }, - { - name: 'inode', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Inode representing the file in the filesystem.', - example: '256383', - }, - { - name: 'mime_type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'MIME type should identify the format of the file or stream of bytes\nusing https://www.iana.org/assignments/media-types/media-types.xhtml[IANA\nofficial types], where possible. When more than one type is applicable, the\nmost specific type should be used.', - default_field: false, - }, - { - name: 'mode', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Mode of the file in octal representation.', - example: '0640', - }, - { - name: 'mtime', - level: 'extended', - type: 'date', - description: 'Last time the file content was modified.', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the file including the extension, without the directory.', - example: 'example.png', - }, - { - name: 'owner', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: "File owner's username.", - example: 'alice', - }, - { - name: 'path', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: - 'Full path to the file, including the file name. It should include\nthe drive letter, when appropriate.', - example: '/home/alice/example.png', - }, - { - name: 'pe.company', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal company name of the file, provided at compile-time.', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'pe.description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal description of the file, provided at compile-time.', - example: 'Paint', - default_field: false, - }, - { - name: 'pe.file_version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal version of the file, provided at compile-time.', - example: '6.3.9600.17415', - default_field: false, - }, - { - name: 'pe.original_file_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal name of the file, provided at compile-time.', - example: 'MSPAINT.EXE', - default_field: false, - }, - { - name: 'pe.product', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal product name of the file, provided at compile-time.', - example: 'Microsoft® Windows® Operating System', - default_field: false, - }, - { - name: 'size', - level: 'extended', - type: 'long', - description: 'File size in bytes.\n\nOnly relevant when `file.type` is "file".', - example: 16384, - }, - { - name: 'target_path', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Target path for symlinks.', - }, - { - name: 'type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'File type (file, dir, or symlink).', - example: 'file', - }, - { - name: 'uid', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The user ID (UID) or security identifier (SID) of the file owner.', - example: '1001', - }, - ], - }, - { - name: 'geo', - title: 'Geo', - group: 2, - description: - 'Geo fields can carry data about a specific location related to an\nevent.\n\nThis geolocation information can be derived from techniques such as Geo IP,\nor be user-supplied.', - type: 'group', - fields: [ - { - name: 'city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - ], - }, - { - name: 'group', - title: 'Group', - group: 2, - description: - 'The group fields are meant to represent groups that are relevant\nto the event.', - type: 'group', - fields: [ - { - name: 'domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - ], - }, - { - name: 'hash', - title: 'Hash', - group: 2, - description: - 'The hash fields represent different hash algorithms and their values.\n\nField names for common hashes (e.g. MD5, SHA1) are predefined. Add fields for\nother hashes by lowercasing the hash algorithm name and using underscore separators\nas appropriate (snake case, e.g. sha3_512).', - type: 'group', - fields: [ - { - name: 'md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'MD5 hash.', - }, - { - name: 'sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA1 hash.', - }, - { - name: 'sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA256 hash.', - }, - { - name: 'sha512', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA512 hash.', - }, - ], - }, - { - name: 'host', - title: 'Host', - group: 2, - description: - 'A host is defined as a general computing instance.\n\nECS host.* fields should be populated with details about the host on which the\nevent happened, or from which the measurement was taken. Host types include\nhardware, virtual machines, Docker containers, and Kubernetes nodes.', - type: 'group', - fields: [ - { - name: 'architecture', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system architecture.', - example: 'x86_64', - }, - { - name: 'domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the domain of which the host is a member.\n\nFor example, on Windows this could be the host Active Directory domain\nor NetBIOS domain name. For Linux this could be the domain of the host\nLDAP provider.', - example: 'CONTOSO', - default_field: false, - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'hostname', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Hostname of the host.\n\nIt normally contains what the `hostname` command returns on the host machine.', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique host id.\n\nAs hostname is not always unique, use values that are meaningful in your environment.\n\nExample: The current usage of `beat.name`.', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: 'Host ip addresses.', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Host mac addresses.', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the host.\n\nIt can contain what `hostname` returns on Unix systems, the fully qualified\ndomain name, or a name specified by the user. The sender decides which value\nto use.', - }, - { - name: 'os.family', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'OS family (such as redhat, debian, freebsd, windows).', - example: 'debian', - }, - { - name: 'os.full', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, including the version or code name.', - example: 'Mac OS Mojave', - }, - { - name: 'os.kernel', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system kernel version as a raw string.', - example: '4.4.0-112-generic', - }, - { - name: 'os.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, without the version.', - example: 'Mac OS X', - }, - { - name: 'os.platform', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'os.version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system version as a raw string.', - example: '10.14.1', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Type of host.\n\nFor Cloud providers this can be the machine type like `t2.medium`. If vm,\nthis could be the container, for example, or other information meaningful\nin your environment.', - }, - { - name: 'uptime', - level: 'extended', - type: 'long', - description: 'Seconds the host has been up.', - example: 1325, - }, - { - name: 'user.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the user is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.email', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'User email address.', - }, - { - name: 'user.full_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'user.group.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.group.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'user.group.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - { - name: 'user.hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'user.id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifiers of the user.', - }, - { - name: 'user.name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'http', - title: 'HTTP', - group: 2, - description: - 'Fields related to HTTP activity. Use the `url` field set to store\nthe url of the request.', - type: 'group', - fields: [ - { - name: 'request.body.bytes', - level: 'extended', - type: 'long', - format: 'bytes', - description: 'Size in bytes of the request body.', - example: 887, - }, - { - name: 'request.body.content', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'The full HTTP request body.', - example: 'Hello world', - }, - { - name: 'request.bytes', - level: 'extended', - type: 'long', - format: 'bytes', - description: 'Total size in bytes of the request (body and headers).', - example: 1437, - }, - { - name: 'request.method', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'HTTP request method.\n\nThe field value must be normalized to lowercase for querying. See the documentation\nsection "Implementing ECS".', - example: 'get, post, put', - }, - { - name: 'request.referrer', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Referrer for this HTTP request.', - example: 'https://blog.example.com/', - }, - { - name: 'response.body.bytes', - level: 'extended', - type: 'long', - format: 'bytes', - description: 'Size in bytes of the response body.', - example: 887, - }, - { - name: 'response.body.content', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'The full HTTP response body.', - example: 'Hello world', - }, - { - name: 'response.bytes', - level: 'extended', - type: 'long', - format: 'bytes', - description: 'Total size in bytes of the response (body and headers).', - example: 1437, - }, - { - name: 'response.status_code', - level: 'extended', - type: 'long', - format: 'string', - description: 'HTTP response status code.', - example: 404, - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'HTTP version.', - example: 1.1, - }, - ], - }, - { - name: 'interface', - title: 'Interface', - group: 2, - description: - 'The interface fields are used to record ingress and egress interface\ninformation when reported by an observer (e.g. firewall, router, load balancer)\nin the context of the observer handling a network connection. In the case of\na single observer interface (e.g. network sensor on a span port) only the observer.ingress\ninformation should be populated.', - type: 'group', - fields: [ - { - name: 'alias', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Interface alias as reported by the system, typically used in firewall\nimplementations for e.g. inside, outside, or dmz logical interface naming.', - example: 'outside', - default_field: false, - }, - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Interface ID as reported by an observer (typically SNMP interface\nID).', - example: 10, - default_field: false, - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Interface name as reported by the system.', - example: 'eth0', - default_field: false, - }, - ], - }, - { - name: 'log', - title: 'Log', - group: 2, - description: - 'Details about the event logging mechanism or logging transport.\n\nThe log.* fields are typically populated with details about the logging mechanism\nused to create and/or transport the event. For example, syslog details belong\nunder `log.syslog.*`.\n\nThe details specific to your event source are typically not logged under `log.*`,\nbut rather in `event.*` or in other ECS fields.', - type: 'group', - fields: [ - { - name: 'level', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Original log level of the log event.\n\nIf the source of the event provides a log level or textual severity, this\nis the one that goes in `log.level`. If your source does not specify one,\nyou may put your event transport severity here (e.g. Syslog severity).\n\nSome examples are `warn`, `err`, `i`, `informational`.', - example: 'error', - }, - { - name: 'logger', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'The name of the logger inside an application. This is usually the\nname of the class which initialized the logger, or can be a custom name.', - example: 'org.elasticsearch.bootstrap.Bootstrap', - }, - { - name: 'origin.file.line', - level: 'extended', - type: 'integer', - description: - 'The line number of the file containing the source code which originated\nthe log event.', - example: 42, - }, - { - name: 'origin.file.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The name of the file containing the source code which originated\nthe log event. Note that this is not the name of the log file.', - example: 'Bootstrap.java', - }, - { - name: 'origin.function', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The name of the function or method which originated the log event.', - example: 'init', - }, - { - name: 'original', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'This is the original log message and contains the full log message\nbefore splitting it up in multiple parts.\n\nIn contrast to the `message` field which can contain an extracted part of\nthe log message, this field contains the original, full log message. It can\nhave already some modifications applied like encoding or new lines removed\nto clean up the log message.\n\nThis field is not indexed and doc_values are disabled so it can not be queried\nbut the value can be retrieved from `_source`.', - example: 'Sep 19 08:26:10 localhost My log', - }, - { - name: 'syslog', - level: 'extended', - type: 'object', - object_type: 'keyword', - description: - 'The Syslog metadata of the event, if the event was transmitted\nvia Syslog. Please see RFCs 5424 or 3164.', - }, - { - name: 'syslog.facility.code', - level: 'extended', - type: 'long', - format: 'string', - description: - 'The Syslog numeric facility of the log event, if available.\n\nAccording to RFCs 5424 and 3164, this value should be an integer between 0\nand 23.', - example: 23, - }, - { - name: 'syslog.facility.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The Syslog text-based facility of the log event, if available.', - example: 'local7', - }, - { - name: 'syslog.priority', - level: 'extended', - type: 'long', - format: 'string', - description: - 'Syslog numeric priority of the event, if available.\n\nAccording to RFCs 5424 and 3164, the priority is 8 * facility + severity.\nThis number is therefore expected to contain a value between 0 and 191.', - example: 135, - }, - { - name: 'syslog.severity.code', - level: 'extended', - type: 'long', - description: - 'The Syslog numeric severity of the log event, if available.\n\nIf the event source publishing via Syslog provides a different numeric severity\nvalue (e.g. firewall, IDS), your source numeric severity should go to `event.severity`.\nIf the event source does not specify a distinct severity, you can optionally\ncopy the Syslog severity to `event.severity`.', - example: 3, - }, - { - name: 'syslog.severity.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The Syslog numeric severity of the log event, if available.\n\nIf the event source publishing via Syslog provides a different severity value\n(e.g. firewall, IDS), your source text severity should go to `log.level`.\nIf the event source does not specify a distinct severity, you can optionally\ncopy the Syslog severity to `log.level`.', - example: 'Error', - }, - ], - }, - { - name: 'network', - title: 'Network', - group: 2, - description: - 'The network is defined as the communication path over which a host\nor network event happens.\n\nThe network.* fields should be populated with details about the network activity\nassociated with an event.', - type: 'group', - fields: [ - { - name: 'application', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A name given to an application level protocol. This can be arbitrarily\nassigned for things like microservices, but also apply to things like skype,\nicq, facebook, twitter. This would be used in situations where the vendor\nor service can be decoded such as from the source/dest IP owners, ports, or\nwire format.\n\nThe field value must be normalized to lowercase for querying. See the documentation\nsection "Implementing ECS".', - example: 'aim', - }, - { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - description: - 'Total bytes transferred in both directions.\n\nIf `source.bytes` and `destination.bytes` are known, `network.bytes` is their\nsum.', - example: 368, - }, - { - name: 'community_id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A hash of source and destination IPs and ports, as well as the\nprotocol used in a communication. This is a tool-agnostic standard to identify\nflows.\n\nLearn more at https://github.com/corelight/community-id-spec.', - example: '1:hO+sN4H+MG5MY/8hIrXPqc4ZQz0=', - }, - { - name: 'direction', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - "Direction of the network traffic.\nRecommended values are:\n * inbound\n * outbound\n * internal\n * external\n * unknown\n\nWhen mapping events from a host-based monitoring context, populate this field from the host's point of view.\nWhen mapping events from a network or perimeter-based monitoring context, populate this field from the point of view of your network perimeter.", - example: 'inbound', - }, - { - name: 'forwarded_ip', - level: 'core', - type: 'ip', - description: 'Host IP address when the source IP address is the proxy.', - example: '192.1.1.2', - }, - { - name: 'iana_number', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'IANA Protocol Number (https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml).\nStandardized list of protocols. This aligns well with NetFlow and sFlow related\nlogs which use the IANA Protocol Number.', - example: 6, - }, - { - name: 'inner', - level: 'extended', - type: 'object', - object_type: 'keyword', - description: - 'Network.inner fields are added in addition to network.vlan fields\nto describe the innermost VLAN when q-in-q VLAN tagging is present. Allowed\nfields include vlan.id and vlan.name. Inner vlan fields are typically used\nwhen sending traffic with multiple 802.1q encapsulations to a network sensor\n(e.g. Zeek, Wireshark.)', - default_field: false, - }, - { - name: 'inner.vlan.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'VLAN ID as reported by the observer.', - example: 10, - default_field: false, - }, - { - name: 'inner.vlan.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Optional VLAN name as reported by the observer.', - example: 'outside', - default_field: false, - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name given by operators to sections of their network.', - example: 'Guest Wifi', - }, - { - name: 'packets', - level: 'core', - type: 'long', - description: - 'Total packets transferred in both directions.\n\nIf `source.packets` and `destination.packets` are known, `network.packets`\nis their sum.', - example: 24, - }, - { - name: 'protocol', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'L7 Network protocol name. ex. http, lumberjack, transport protocol.\n\nThe field value must be normalized to lowercase for querying. See the documentation\nsection "Implementing ECS".', - example: 'http', - }, - { - name: 'transport', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Same as network.iana_number, but instead using the Keyword name\nof the transport layer (udp, tcp, ipv6-icmp, etc.)\n\nThe field value must be normalized to lowercase for querying. See the documentation\nsection "Implementing ECS".', - example: 'tcp', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'In the OSI Model this would be the Network Layer. ipv4, ipv6,\nipsec, pim, etc\n\nThe field value must be normalized to lowercase for querying. See the documentation\nsection "Implementing ECS".', - example: 'ipv4', - }, - { - name: 'vlan.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'VLAN ID as reported by the observer.', - example: 10, - default_field: false, - }, - { - name: 'vlan.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Optional VLAN name as reported by the observer.', - example: 'outside', - default_field: false, - }, - ], - }, - { - name: 'observer', - title: 'Observer', - group: 2, - description: - 'An observer is defined as a special network, security, or application\ndevice used to detect, observe, or create network, security, or application-related\nevents and metrics.\n\nThis could be a custom hardware appliance or a server that has been configured\nto run special network, security, or application software. Examples include\nfirewalls, web proxies, intrusion detection/prevention systems, network monitoring\nsensors, web application firewalls, data loss prevention systems, and APM servers.\nThe observer.* fields shall be populated with details of the system, if any,\nthat detects, observes and/or creates a network, security, or application event\nor metric. Message queues and ETL components used in processing events or metrics\nare not considered observers in ECS.', - type: 'group', - fields: [ - { - name: 'egress', - level: 'extended', - type: 'object', - object_type: 'keyword', - description: - 'Observer.egress holds information like interface number and name,\nvlan, and zone information to classify egress traffic. Single armed monitoring\nsuch as a network sensor on a span port should only use observer.ingress\nto categorize traffic.', - default_field: false, - }, - { - name: 'egress.interface.alias', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Interface alias as reported by the system, typically used in firewall\nimplementations for e.g. inside, outside, or dmz logical interface naming.', - example: 'outside', - default_field: false, - }, - { - name: 'egress.interface.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Interface ID as reported by an observer (typically SNMP interface\nID).', - example: 10, - default_field: false, - }, - { - name: 'egress.interface.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Interface name as reported by the system.', - example: 'eth0', - default_field: false, - }, - { - name: 'egress.vlan.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'VLAN ID as reported by the observer.', - example: 10, - default_field: false, - }, - { - name: 'egress.vlan.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Optional VLAN name as reported by the observer.', - example: 'outside', - default_field: false, - }, - { - name: 'egress.zone', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Network zone of outbound traffic as reported by the observer to\ncategorize the destination area of egress traffic, e.g. Internal, External,\nDMZ, HR, Legal, etc.', - example: 'Public_Internet', - default_field: false, - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'hostname', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Hostname of the observer.', - }, - { - name: 'ingress', - level: 'extended', - type: 'object', - object_type: 'keyword', - description: - 'Observer.ingress holds information like interface number and name,\nvlan, and zone information to classify ingress traffic. Single armed monitoring\nsuch as a network sensor on a span port should only use observer.ingress\nto categorize traffic.', - default_field: false, - }, - { - name: 'ingress.interface.alias', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Interface alias as reported by the system, typically used in firewall\nimplementations for e.g. inside, outside, or dmz logical interface naming.', - example: 'outside', - default_field: false, - }, - { - name: 'ingress.interface.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Interface ID as reported by an observer (typically SNMP interface\nID).', - example: 10, - default_field: false, - }, - { - name: 'ingress.interface.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Interface name as reported by the system.', - example: 'eth0', - default_field: false, - }, - { - name: 'ingress.vlan.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'VLAN ID as reported by the observer.', - example: 10, - default_field: false, - }, - { - name: 'ingress.vlan.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Optional VLAN name as reported by the observer.', - example: 'outside', - default_field: false, - }, - { - name: 'ingress.zone', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Network zone of incoming traffic as reported by the observer to\ncategorize the source area of ingress traffic. e.g. internal, External, DMZ,\nHR, Legal, etc.', - example: 'DMZ', - default_field: false, - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: 'IP addresses of the observer.', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'MAC addresses of the observer', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Custom name of the observer.\n\nThis is a name that can be given to an observer. This can be helpful for example\nif multiple firewalls of the same model are used in an organization.\n\nIf no custom name is needed, the field can be left empty.', - example: '1_proxySG', - }, - { - name: 'os.family', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'OS family (such as redhat, debian, freebsd, windows).', - example: 'debian', - }, - { - name: 'os.full', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, including the version or code name.', - example: 'Mac OS Mojave', - }, - { - name: 'os.kernel', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system kernel version as a raw string.', - example: '4.4.0-112-generic', - }, - { - name: 'os.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, without the version.', - example: 'Mac OS X', - }, - { - name: 'os.platform', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'os.version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system version as a raw string.', - example: '10.14.1', - }, - { - name: 'product', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The product name of the observer.', - example: 's200', - }, - { - name: 'serial_number', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Observer serial number.', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'The type of the observer the data is coming from.\n\nThere is no predefined list of observer types. Some examples are `forwarder`,\n`firewall`, `ids`, `ips`, `proxy`, `poller`, `sensor`, `APM server`.', - example: 'firewall', - }, - { - name: 'vendor', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Vendor name of the observer.', - example: 'Symantec', - }, - { - name: 'version', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Observer version.', - }, - ], - }, - { - name: 'organization', - title: 'Organization', - group: 2, - description: - 'The organization fields enrich data with information about the company\nor entity the data is associated with.\n\nThese fields help you arrange or filter data stored in an index by one or multiple\norganizations.', - type: 'group', - fields: [ - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the organization.', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Organization name.', - }, - ], - }, - { - name: 'os', - title: 'Operating System', - group: 2, - description: 'The OS fields contain information about the operating system.', - type: 'group', - fields: [ - { - name: 'family', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'OS family (such as redhat, debian, freebsd, windows).', - example: 'debian', - }, - { - name: 'full', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, including the version or code name.', - example: 'Mac OS Mojave', - }, - { - name: 'kernel', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system kernel version as a raw string.', - example: '4.4.0-112-generic', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, without the version.', - example: 'Mac OS X', - }, - { - name: 'platform', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system version as a raw string.', - example: '10.14.1', - }, - ], - }, - { - name: 'package', - title: 'Package', - group: 2, - description: - 'These fields contain information about an installed software package.\nIt contains general information about a package, such as name, version or size.\nIt also contains installation details, such as time or location.', - type: 'group', - fields: [ - { - name: 'architecture', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Package architecture.', - example: 'x86_64', - }, - { - name: 'build_version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Additional information about the build version of the installed\npackage.\n\nFor example use the commit SHA of a non-released package.', - example: '36f4f7e89dd61b0988b12ee000b98966867710cd', - default_field: false, - }, - { - name: 'checksum', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Checksum of the installed package for verification.', - example: '68b329da9893e34099c7d8ad5cb9c940', - }, - { - name: 'description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Description of the package.', - example: - 'Open source programming language to build simple/reliable/efficient\nsoftware.', - }, - { - name: 'install_scope', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Indicating how the package was installed, e.g. user-local, global.', - example: 'global', - }, - { - name: 'installed', - level: 'extended', - type: 'date', - description: 'Time when package was installed.', - }, - { - name: 'license', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'License under which the package was released.\n\nUse a short name, e.g. the license identifier from SPDX License List where\npossible (https://spdx.org/licenses/).', - example: 'Apache License 2.0', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Package name', - example: 'go', - }, - { - name: 'path', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Path where the package is installed.', - example: '/usr/local/Cellar/go/1.12.9/', - }, - { - name: 'reference', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Home page or reference URL of the software in this package, if\navailable.', - example: 'https://golang.org', - default_field: false, - }, - { - name: 'size', - level: 'extended', - type: 'long', - format: 'string', - description: 'Package size in bytes.', - example: 62231, - }, - { - name: 'type', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Type of package.\n\nThis should contain the package file type, rather than the package manager\nname. Examples: rpm, dpkg, brew, npm, gem, nupkg, jar.', - example: 'rpm', - default_field: false, - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Package version', - example: '1.12.9', - }, - ], - }, - { - name: 'pe', - title: 'PE Header', - group: 2, - description: 'These fields contain Windows Portable Executable (PE) metadata.', - type: 'group', - fields: [ - { - name: 'company', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal company name of the file, provided at compile-time.', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal description of the file, provided at compile-time.', - example: 'Paint', - default_field: false, - }, - { - name: 'file_version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal version of the file, provided at compile-time.', - example: '6.3.9600.17415', - default_field: false, - }, - { - name: 'original_file_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal name of the file, provided at compile-time.', - example: 'MSPAINT.EXE', - default_field: false, - }, - { - name: 'product', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal product name of the file, provided at compile-time.', - example: 'Microsoft® Windows® Operating System', - default_field: false, - }, - ], - }, - { - name: 'process', - title: 'Process', - group: 2, - description: - 'These fields contain information about a process.\n\nThese fields can help you correlate metrics information with a process id/name\nfrom a log message. The `process.pid` often stays in the metric itself and\nis copied to the global field for correlation.', - type: 'group', - fields: [ - { - name: 'args', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.', - example: ['/usr/bin/ssh', '-l', 'user', '10.0.0.16'], - }, - { - name: 'args_count', - level: 'extended', - type: 'long', - description: - 'Length of the process.args array.\n\nThis field can be useful for querying or performing bucket analysis on how\nmany arguments were provided to start a process. More arguments may be an\nindication of suspicious activity.', - example: 4, - default_field: false, - }, - { - name: 'code_signature.exists', - level: 'core', - type: 'boolean', - description: 'Boolean to capture if a signature is present.', - example: 'true', - default_field: false, - }, - { - name: 'code_signature.status', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Additional information about the certificate status.\n\nThis is useful for logging cryptographic errors with the certificate validity\nor trust status. Leave unpopulated if the validity or trust of the certificate\nwas unchecked.', - example: 'ERROR_UNTRUSTED_ROOT', - default_field: false, - }, - { - name: 'code_signature.subject_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Subject name of the code signer', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'code_signature.trusted', - level: 'extended', - type: 'boolean', - description: - 'Stores the trust status of the certificate chain.\n\nValidating the trust of the certificate chain may be complicated, and this\nfield should only be populated by tools that actively check the status.', - example: 'true', - default_field: false, - }, - { - name: 'code_signature.valid', - level: 'extended', - type: 'boolean', - description: - 'Boolean to capture if the digital signature is verified against\nthe binary content.\n\nLeave unpopulated if a certificate was unchecked.', - example: 'true', - default_field: false, - }, - { - name: 'command_line', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: - 'Full command line that started the process, including the absolute\npath to the executable, and all arguments.\n\nSome arguments may be filtered to protect sensitive information.', - example: '/usr/bin/ssh -l user 10.0.0.16', - default_field: false, - }, - { - name: 'entity_id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique identifier for the process.\n\nThe implementation of this is specified by the data source, but some examples\nof what could be used here are a process-generated UUID, Sysmon Process GUIDs,\nor a hash of some uniquely identifying components of a process.\n\nConstructing a globally unique identifier is a common practice to mitigate\nPID reuse as well as to identify a specific process over time, across multiple\nmonitored hosts.', - example: 'c2c455d9f99375d', - default_field: false, - }, - { - name: 'executable', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Absolute path to the process executable.', - example: '/usr/bin/ssh', - }, - { - name: 'exit_code', - level: 'extended', - type: 'long', - description: - 'The exit code of the process, if this is a termination event.\n\nThe field should be absent if there is no exit code for the event (e.g. process\nstart).', - example: 137, - default_field: false, - }, - { - name: 'hash.md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'MD5 hash.', - }, - { - name: 'hash.sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA1 hash.', - }, - { - name: 'hash.sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA256 hash.', - }, - { - name: 'hash.sha512', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA512 hash.', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Process name.\n\nSometimes called program name or similar.', - example: 'ssh', - }, - { - name: 'parent.args', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Array of process arguments.\n\nMay be filtered to protect sensitive information.', - example: ['ssh', '-l', 'user', '10.0.0.16'], - default_field: false, - }, - { - name: 'parent.args_count', - level: 'extended', - type: 'long', - description: - 'Length of the process.args array.\n\nThis field can be useful for querying or performing bucket analysis on how\nmany arguments were provided to start a process. More arguments may be an\nindication of suspicious activity.', - example: 4, - default_field: false, - }, - { - name: 'parent.code_signature.exists', - level: 'core', - type: 'boolean', - description: 'Boolean to capture if a signature is present.', - example: 'true', - default_field: false, - }, - { - name: 'parent.code_signature.status', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Additional information about the certificate status.\n\nThis is useful for logging cryptographic errors with the certificate validity\nor trust status. Leave unpopulated if the validity or trust of the certificate\nwas unchecked.', - example: 'ERROR_UNTRUSTED_ROOT', - default_field: false, - }, - { - name: 'parent.code_signature.subject_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Subject name of the code signer', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'parent.code_signature.trusted', - level: 'extended', - type: 'boolean', - description: - 'Stores the trust status of the certificate chain.\n\nValidating the trust of the certificate chain may be complicated, and this\nfield should only be populated by tools that actively check the status.', - example: 'true', - default_field: false, - }, - { - name: 'parent.code_signature.valid', - level: 'extended', - type: 'boolean', - description: - 'Boolean to capture if the digital signature is verified against\nthe binary content.\n\nLeave unpopulated if a certificate was unchecked.', - example: 'true', - default_field: false, - }, - { - name: 'parent.command_line', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: - 'Full command line that started the process, including the absolute\npath to the executable, and all arguments.\n\nSome arguments may be filtered to protect sensitive information.', - example: '/usr/bin/ssh -l user 10.0.0.16', - default_field: false, - }, - { - name: 'parent.entity_id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique identifier for the process.\n\nThe implementation of this is specified by the data source, but some examples\nof what could be used here are a process-generated UUID, Sysmon Process GUIDs,\nor a hash of some uniquely identifying components of a process.\n\nConstructing a globally unique identifier is a common practice to mitigate\nPID reuse as well as to identify a specific process over time, across multiple\nmonitored hosts.', - example: 'c2c455d9f99375d', - default_field: false, - }, - { - name: 'parent.executable', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: 'Absolute path to the process executable.', - example: '/usr/bin/ssh', - default_field: false, - }, - { - name: 'parent.exit_code', - level: 'extended', - type: 'long', - description: - 'The exit code of the process, if this is a termination event.\n\nThe field should be absent if there is no exit code for the event (e.g. process\nstart).', - example: 137, - default_field: false, - }, - { - name: 'parent.hash.md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'MD5 hash.', - default_field: false, - }, - { - name: 'parent.hash.sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA1 hash.', - default_field: false, - }, - { - name: 'parent.hash.sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA256 hash.', - default_field: false, - }, - { - name: 'parent.hash.sha512', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'SHA512 hash.', - default_field: false, - }, - { - name: 'parent.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: 'Process name.\n\nSometimes called program name or similar.', - example: 'ssh', - default_field: false, - }, - { - name: 'parent.pgid', - level: 'extended', - type: 'long', - format: 'string', - description: 'Identifier of the group of processes the process belongs to.', - default_field: false, - }, - { - name: 'parent.pid', - level: 'core', - type: 'long', - format: 'string', - description: 'Process id.', - example: 4242, - default_field: false, - }, - { - name: 'parent.ppid', - level: 'extended', - type: 'long', - format: 'string', - description: "Parent process' pid.", - example: 4241, - default_field: false, - }, - { - name: 'parent.start', - level: 'extended', - type: 'date', - description: 'The time the process started.', - example: '2016-05-23T08:05:34.853Z', - default_field: false, - }, - { - name: 'parent.thread.id', - level: 'extended', - type: 'long', - format: 'string', - description: 'Thread ID.', - example: 4242, - default_field: false, - }, - { - name: 'parent.thread.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Thread name.', - example: 'thread-0', - default_field: false, - }, - { - name: 'parent.title', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: - 'Process title.\n\nThe proctitle, some times the same as process name. Can also be different:\nfor example a browser setting its title to the web page currently opened.', - default_field: false, - }, - { - name: 'parent.uptime', - level: 'extended', - type: 'long', - description: 'Seconds the process has been up.', - example: 1325, - default_field: false, - }, - { - name: 'parent.working_directory', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: 'The working directory of the process.', - example: '/home/alice', - default_field: false, - }, - { - name: 'pe.company', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal company name of the file, provided at compile-time.', - example: 'Microsoft Corporation', - default_field: false, - }, - { - name: 'pe.description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal description of the file, provided at compile-time.', - example: 'Paint', - default_field: false, - }, - { - name: 'pe.file_version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal version of the file, provided at compile-time.', - example: '6.3.9600.17415', - default_field: false, - }, - { - name: 'pe.original_file_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal name of the file, provided at compile-time.', - example: 'MSPAINT.EXE', - default_field: false, - }, - { - name: 'pe.product', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Internal product name of the file, provided at compile-time.', - example: 'Microsoft® Windows® Operating System', - default_field: false, - }, - { - name: 'pgid', - level: 'extended', - type: 'long', - format: 'string', - description: 'Identifier of the group of processes the process belongs to.', - }, - { - name: 'pid', - level: 'core', - type: 'long', - format: 'string', - description: 'Process id.', - example: 4242, - }, - { - name: 'ppid', - level: 'extended', - type: 'long', - format: 'string', - description: "Parent process' pid.", - example: 4241, - }, - { - name: 'start', - level: 'extended', - type: 'date', - description: 'The time the process started.', - example: '2016-05-23T08:05:34.853Z', - }, - { - name: 'thread.id', - level: 'extended', - type: 'long', - format: 'string', - description: 'Thread ID.', - example: 4242, - }, - { - name: 'thread.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Thread name.', - example: 'thread-0', - }, - { - name: 'title', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: - 'Process title.\n\nThe proctitle, some times the same as process name. Can also be different:\nfor example a browser setting its title to the web page currently opened.', - }, - { - name: 'uptime', - level: 'extended', - type: 'long', - description: 'Seconds the process has been up.', - example: 1325, - }, - { - name: 'working_directory', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'The working directory of the process.', - example: '/home/alice', - }, - ], - }, - { - name: 'registry', - title: 'Registry', - group: 2, - description: 'Fields related to Windows Registry operations.', - type: 'group', - fields: [ - { - name: 'data.bytes', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Original bytes written with base64 encoding.\n\nFor Windows registry operations, such as SetValueEx and RegQueryValueEx, this\ncorresponds to the data pointed by `lp_data`. This is optional but provides\nbetter recoverability and should be populated for REG_BINARY encoded values.', - example: 'ZQBuAC0AVQBTAAAAZQBuAAAAAAA=', - default_field: false, - }, - { - name: 'data.strings', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Content when writing string types.\n\nPopulated as an array when writing string data to the registry. For single\nstring registry types (REG_SZ, REG_EXPAND_SZ), this should be an array with\none string. For sequences of string with REG_MULTI_SZ, this array will be\nvariable length. For numeric data, such as REG_DWORD and REG_QWORD, this should\nbe populated with the decimal representation (e.g `"1"`).', - example: '["C:\\rta\\red_ttp\\bin\\myapp.exe"]', - default_field: false, - }, - { - name: 'data.type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Standard registry type for encoding contents', - example: 'REG_SZ', - default_field: false, - }, - { - name: 'hive', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Abbreviated name for the hive.', - example: 'HKLM', - default_field: false, - }, - { - name: 'key', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Hive-relative path of keys.', - example: - 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\winword.exe', - default_field: false, - }, - { - name: 'path', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Full path, including hive, key and value', - example: - 'HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution\nOptions\\winword.exe\\Debugger', - default_field: false, - }, - { - name: 'value', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the value written.', - example: 'Debugger', - default_field: false, - }, - ], - }, - { - name: 'related', - title: 'Related', - group: 2, - description: - 'This field set is meant to facilitate pivoting around a piece of\ndata.\n\nSome pieces of information can be seen in many places in an ECS event. To facilitate\nsearching for them, store an array of all seen values to their corresponding\nfield in `related.`.\n\nA concrete example is IP addresses, which can be under host, observer, source,\ndestination, client, server, and network.forwarded_ip. If you append all IPs\nto `related.ip`, you can then search for a given IP trivially, no matter where\nit appeared, by querying `related.ip:192.0.2.15`.', - type: 'group', - fields: [ - { - name: 'hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - "All the hashes seen on your event. Populating this field, then\nusing it to search for hashes can help in situations where you're unsure what\nthe hash algorithm is (and therefore which key name to search).", - default_field: false, - }, - { - name: 'ip', - level: 'extended', - type: 'ip', - description: 'All of the IPs seen on your event.', - }, - { - name: 'user', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'All the user names seen on your event.', - default_field: false, - }, - ], - }, - { - name: 'rule', - title: 'Rule', - group: 2, - description: - 'Rule fields are used to capture the specifics of any observer or\nagent rules that generate alerts or other notable events.\n\nExamples of data sources that would populate the rule fields include: network\nadmission control platforms, network or host IDS/IPS, network firewalls, web\napplication firewalls, url filters, endpoint detection and response (EDR) systems,\netc.', - type: 'group', - fields: [ - { - name: 'author', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name, organization, or pseudonym of the author or authors who created\nthe rule used to generate this event.', - example: ['Star-Lord'], - default_field: false, - }, - { - name: 'category', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A categorization value keyword used by the entity using the rule\nfor detection of this event.', - example: 'Attempted Information Leak', - default_field: false, - }, - { - name: 'description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The description of the rule generating the event.', - example: 'Block requests to public DNS over HTTPS / TLS protocols', - default_field: false, - }, - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A rule ID that is unique within the scope of an agent, observer,\nor other entity using the rule for detection of this event.', - example: 101, - default_field: false, - }, - { - name: 'license', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the license under which the rule used to generate this\nevent is made available.', - example: 'Apache 2.0', - default_field: false, - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The name of the rule or signature generating the event.', - example: 'BLOCK_DNS_over_TLS', - default_field: false, - }, - { - name: 'reference', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Reference URL to additional information about the rule used to\ngenerate this event.\n\nThe URL can point to the vendor documentation about the rule. If that is\nnot available, it can also be a link to a more general page describing this\ntype of alert.', - example: 'https://en.wikipedia.org/wiki/DNS_over_TLS', - default_field: false, - }, - { - name: 'ruleset', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the ruleset, policy, group, or parent category in which\nthe rule used to generate this event is a member.', - example: 'Standard_Protocol_Filters', - default_field: false, - }, - { - name: 'uuid', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A rule ID that is unique within the scope of a set or group of\nagents, observers, or other entities using the rule for detection of this\nevent.', - example: 1100110011, - default_field: false, - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The version / revision of the rule being used for analysis.', - example: 1.1, - default_field: false, - }, - ], - }, - { - name: 'server', - title: 'Server', - group: 2, - description: - 'A Server is defined as the responder in a network connection for\nevents regarding sessions, connections, or bidirectional flow records.\n\nFor TCP events, the server is the receiver of the initial SYN packet(s) of the\nTCP connection. For other protocols, the server is generally the responder in\nthe network transaction. Some systems actually use the term "responder" to refer\nthe server in TCP connections. The server fields describe details about the\nsystem acting as the server in the network event. Server fields are usually\npopulated in conjunction with client fields. Server fields are generally not\npopulated for packet-level events.\n\nClient / server representations can add semantic context to an exchange, which\nis helpful to visualize the data in certain situations. If your context falls\nin that category, you should still ensure that source and destination are filled\nappropriately.', - type: 'group', - fields: [ - { - name: 'address', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Some event server addresses are defined ambiguously. The event\nwill sometimes list an IP, a domain or a unix socket. You should always store\nthe raw address in the `.address` field.\n\nThen it should be duplicated to `.ip` or `.domain`, depending on which one\nit is.', - }, - { - name: 'as.number', - level: 'extended', - type: 'long', - description: - 'Unique number allocated to the autonomous system. The autonomous\nsystem number (ASN) uniquely identifies each network on the Internet.', - example: 15169, - }, - { - name: 'as.organization.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Organization name.', - example: 'Google LLC', - }, - { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - description: 'Bytes sent from the server to the client.', - example: 184, - }, - { - name: 'domain', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Server domain.', - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: - 'IP address of the server.\n\nCan be one or multiple IPv4 or IPv6 addresses.', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'MAC address of the server.', - }, - { - name: 'nat.ip', - level: 'extended', - type: 'ip', - description: - 'Translated ip of destination based NAT sessions (e.g. internet\nto private DMZ)\n\nTypically used with load balancers, firewalls, or routers.', - }, - { - name: 'nat.port', - level: 'extended', - type: 'long', - format: 'string', - description: - 'Translated port of destination based NAT sessions (e.g. internet\nto private DMZ)\n\nTypically used with load balancers, firewalls, or routers.', - }, - { - name: 'packets', - level: 'core', - type: 'long', - description: 'Packets sent from the server to the client.', - example: 12, - }, - { - name: 'port', - level: 'core', - type: 'long', - format: 'string', - description: 'Port of the server.', - }, - { - name: 'registered_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The highest registered server domain, stripped of the subdomain.\n\nFor example, the registered domain for "foo.google.com" is "google.com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last two labels will not work well for TLDs such as "co.uk".', - example: 'google.com', - }, - { - name: 'top_level_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The effective top level domain (eTLD), also known as the domain\nsuffix, is the last part of the domain name. For example, the top level domain\nfor google.com is "com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last label will not work well for effective TLDs such as "co.uk".', - example: 'co.uk', - }, - { - name: 'user.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the user is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.email', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'User email address.', - }, - { - name: 'user.full_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'user.group.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.group.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'user.group.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - { - name: 'user.hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'user.id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifiers of the user.', - }, - { - name: 'user.name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'service', - title: 'Service', - group: 2, - description: - 'The service fields describe the service for or from which the data\nwas collected.\n\nThese fields help you find and correlate logs for a specific service and version.', - type: 'group', - fields: [ - { - name: 'ephemeral_id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Ephemeral identifier of this service (if one exists).\n\nThis id normally changes across restarts, but `service.id` does not.', - example: '8a4f500f', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique identifier of the running service. If the service is comprised\nof many nodes, the `service.id` should be the same for all nodes.\n\nThis id should uniquely identify the service. This makes it possible to correlate\nlogs and metrics for one specific service, no matter which particular node\nemitted the event.\n\nNote that if you need to see the events from one specific host of the service,\nyou should filter on that `host.name` or `host.id` instead.', - example: 'd37e5ebfe0ae6c4972dbe9f0174a1637bb8247f6', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the service data is collected from.\n\nThe name of the service is normally user given. This allows for distributed\nservices that run on multiple hosts to correlate the related instances based\non the name.\n\nIn the case of Elasticsearch the `service.name` could contain the cluster\nname. For Beats the `service.name` is by default a copy of the `service.type`\nfield if no name is specified.', - example: 'elasticsearch-metrics', - }, - { - name: 'node.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of a service node.\n\nThis allows for two nodes of the same service running on the same host to\nbe differentiated. Therefore, `service.node.name` should typically be unique\nacross nodes of a given service.\n\nIn the case of Elasticsearch, the `service.node.name` could contain the unique\nnode name within the Elasticsearch cluster. In cases where the service doe not\nhave the concept of a node name, the host name or container name can be used\nto distinguish running instances that make up this service. If those do not\nprovide uniqueness (e.g. multiple instances of the service running on the\nsame host) - the node name can be manually set.', - example: 'instance-0000000016', - }, - { - name: 'state', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Current state of the service.', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'The type of the service data is collected from.\n\nThe type can be used to group and correlate logs and metrics from one service\ntype.\n\nExample: If logs or metrics are collected from Elasticsearch, `service.type`\nwould be `elasticsearch`.', - example: 'elasticsearch', - }, - { - name: 'version', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: - 'Version of the service the data was collected from.\n\nThis allows to look at a data set only for a specific version of a service.', - example: '3.2.4', - }, - ], - }, - { - name: 'source', - title: 'Source', - group: 2, - description: - 'Source fields describe details about the source of a packet/event.\n\nSource fields are usually populated in conjunction with destination fields.', - type: 'group', - fields: [ - { - name: 'address', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Some event source addresses are defined ambiguously. The event\nwill sometimes list an IP, a domain or a unix socket. You should always store\nthe raw address in the `.address` field.\n\nThen it should be duplicated to `.ip` or `.domain`, depending on which one\nit is.', - }, - { - name: 'as.number', - level: 'extended', - type: 'long', - description: - 'Unique number allocated to the autonomous system. The autonomous\nsystem number (ASN) uniquely identifies each network on the Internet.', - example: 15169, - }, - { - name: 'as.organization.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Organization name.', - example: 'Google LLC', - }, - { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - description: 'Bytes sent from the source to the destination.', - example: 184, - }, - { - name: 'domain', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Source domain.', - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: - 'IP address of the source.\n\nCan be one or multiple IPv4 or IPv6 addresses.', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'MAC address of the source.', - }, - { - name: 'nat.ip', - level: 'extended', - type: 'ip', - description: - 'Translated ip of source based NAT sessions (e.g. internal client\nto internet)\n\nTypically connections traversing load balancers, firewalls, or routers.', - }, - { - name: 'nat.port', - level: 'extended', - type: 'long', - format: 'string', - description: - 'Translated port of source based NAT sessions. (e.g. internal client\nto internet)\n\nTypically used with load balancers, firewalls, or routers.', - }, - { - name: 'packets', - level: 'core', - type: 'long', - description: 'Packets sent from the source to the destination.', - example: 12, - }, - { - name: 'port', - level: 'core', - type: 'long', - format: 'string', - description: 'Port of the source.', - }, - { - name: 'registered_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The highest registered source domain, stripped of the subdomain.\n\nFor example, the registered domain for "foo.google.com" is "google.com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last two labels will not work well for TLDs such as "co.uk".', - example: 'google.com', - }, - { - name: 'top_level_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The effective top level domain (eTLD), also known as the domain\nsuffix, is the last part of the domain name. For example, the top level domain\nfor google.com is "com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last label will not work well for effective TLDs such as "co.uk".', - example: 'co.uk', - }, - { - name: 'user.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the user is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.email', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'User email address.', - }, - { - name: 'user.full_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'user.group.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'user.group.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'user.group.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - { - name: 'user.hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'user.id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifiers of the user.', - }, - { - name: 'user.name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'threat', - title: 'Threat', - group: 2, - description: - 'Fields to classify events and alerts according to a threat taxonomy\nsuch as the Mitre ATT&CK framework.\n\nThese fields are for users to classify alerts from all of their sources (e.g.\nIDS, NGFW, etc.) within a common taxonomy. The threat.tactic.* are meant to\ncapture the high level category of the threat (e.g. "impact"). The threat.technique.*\nfields are meant to capture which kind of approach is used by this detected\nthreat, to accomplish the goal (e.g. "endpoint denial of service").', - type: 'group', - fields: [ - { - name: 'framework', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the threat framework used to further categorize and classify\nthe tactic and technique of the reported threat. Framework classification\ncan be provided by detecting systems, evaluated at ingest time, or retrospectively\ntagged to events.', - example: 'MITRE ATT&CK', - }, - { - name: 'tactic.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The id of tactic used by this threat. You can use the Mitre ATT&CK\nMatrix Tactic categorization, for example. (ex. https://attack.mitre.org/tactics/TA0040/\n)', - example: 'TA0040', - }, - { - name: 'tactic.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the type of tactic used by this threat. You can use the\nMitre ATT&CK Matrix Tactic categorization, for example. (ex. https://attack.mitre.org/tactics/TA0040/\n)', - example: 'impact', - }, - { - name: 'tactic.reference', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The reference url of tactic used by this threat. You can use the\nMitre ATT&CK Matrix Tactic categorization, for example. (ex. https://attack.mitre.org/tactics/TA0040/\n)', - example: 'https://attack.mitre.org/tactics/TA0040/', - }, - { - name: 'technique.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The id of technique used by this tactic. You can use the Mitre\nATT&CK Matrix Tactic categorization, for example. (ex. https://attack.mitre.org/techniques/T1499/\n)', - example: 'T1499', - }, - { - name: 'technique.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: - 'The name of technique used by this tactic. You can use the Mitre\nATT&CK Matrix Tactic categorization, for example. (ex. https://attack.mitre.org/techniques/T1499/\n)', - example: 'endpoint denial of service', - }, - { - name: 'technique.reference', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The reference url of technique used by this tactic. You can use\nthe Mitre ATT&CK Matrix Tactic categorization, for example. (ex. https://attack.mitre.org/techniques/T1499/\n)', - example: 'https://attack.mitre.org/techniques/T1499/', - }, - ], - }, - { - name: 'tls', - title: 'TLS', - group: 2, - description: - 'Fields related to a TLS connection. These fields focus on the TLS\nprotocol itself and intentionally avoids in-depth analysis of the related x.509\ncertificate files.', - type: 'group', - fields: [ - { - name: 'cipher', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'String indicating the cipher used during the current connection.', - example: 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256', - default_field: false, - }, - { - name: 'client.certificate', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'PEM-encoded stand-alone certificate offered by the client. This\nis usually mutually-exclusive of `client.certificate_chain` since this value\nalso exists in that list.', - example: 'MII...', - default_field: false, - }, - { - name: 'client.certificate_chain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Array of PEM-encoded certificates that make up the certificate\nchain offered by the client. This is usually mutually-exclusive of `client.certificate`\nsince that value should be the first certificate in the chain.', - example: ['MII...', 'MII...'], - default_field: false, - }, - { - name: 'client.hash.md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Certificate fingerprint using the MD5 digest of DER-encoded version\nof certificate offered by the client. For consistency with other hash values,\nthis value should be formatted as an uppercase hash.', - example: '0F76C7F2C55BFD7D8E8B8F4BFBF0C9EC', - default_field: false, - }, - { - name: 'client.hash.sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Certificate fingerprint using the SHA1 digest of DER-encoded version\nof certificate offered by the client. For consistency with other hash values,\nthis value should be formatted as an uppercase hash.', - example: '9E393D93138888D288266C2D915214D1D1CCEB2A', - default_field: false, - }, - { - name: 'client.hash.sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Certificate fingerprint using the SHA256 digest of DER-encoded\nversion of certificate offered by the client. For consistency with other hash\nvalues, this value should be formatted as an uppercase hash.', - example: '0687F666A054EF17A08E2F2162EAB4CBC0D265E1D7875BE74BF3C712CA92DAF0', - default_field: false, - }, - { - name: 'client.issuer', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Distinguished name of subject of the issuer of the x.509 certificate\npresented by the client.', - example: 'CN=MyDomain Root CA, OU=Infrastructure Team, DC=mydomain, DC=com', - default_field: false, - }, - { - name: 'client.ja3', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A hash that identifies clients based on how they perform an SSL/TLS\nhandshake.', - example: 'd4e5b18d6b55c71272893221c96ba240', - default_field: false, - }, - { - name: 'client.not_after', - level: 'extended', - type: 'date', - description: - 'Date/Time indicating when client certificate is no longer considered\nvalid.', - example: '2021-01-01T00:00:00.000Z', - default_field: false, - }, - { - name: 'client.not_before', - level: 'extended', - type: 'date', - description: 'Date/Time indicating when client certificate is first considered\nvalid.', - example: '1970-01-01T00:00:00.000Z', - default_field: false, - }, - { - name: 'client.server_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Also called an SNI, this tells the server which hostname to which\nthe client is attempting to connect. When this value is available, it should\nget copied to `destination.domain`.', - example: 'www.elastic.co', - default_field: false, - }, - { - name: 'client.subject', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Distinguished name of subject of the x.509 certificate presented\nby the client.', - example: 'CN=myclient, OU=Documentation Team, DC=mydomain, DC=com', - default_field: false, - }, - { - name: 'client.supported_ciphers', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Array of ciphers offered by the client during the client hello.', - example: [ - 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384', - 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384', - '...', - ], - default_field: false, - }, - { - name: 'curve', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'String indicating the curve used for the given cipher, when applicable.', - example: 'secp256r1', - default_field: false, - }, - { - name: 'established', - level: 'extended', - type: 'boolean', - description: - 'Boolean flag indicating if the TLS negotiation was successful and\ntransitioned to an encrypted tunnel.', - default_field: false, - }, - { - name: 'next_protocol', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'String indicating the protocol being tunneled. Per the values in\nthe IANA registry (https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids),\nthis string should be lower case.', - example: 'http/1.1', - default_field: false, - }, - { - name: 'resumed', - level: 'extended', - type: 'boolean', - description: - 'Boolean flag indicating if this TLS connection was resumed from\nan existing TLS negotiation.', - default_field: false, - }, - { - name: 'server.certificate', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'PEM-encoded stand-alone certificate offered by the server. This\nis usually mutually-exclusive of `server.certificate_chain` since this value\nalso exists in that list.', - example: 'MII...', - default_field: false, - }, - { - name: 'server.certificate_chain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Array of PEM-encoded certificates that make up the certificate\nchain offered by the server. This is usually mutually-exclusive of `server.certificate`\nsince that value should be the first certificate in the chain.', - example: ['MII...', 'MII...'], - default_field: false, - }, - { - name: 'server.hash.md5', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Certificate fingerprint using the MD5 digest of DER-encoded version\nof certificate offered by the server. For consistency with other hash values,\nthis value should be formatted as an uppercase hash.', - example: '0F76C7F2C55BFD7D8E8B8F4BFBF0C9EC', - default_field: false, - }, - { - name: 'server.hash.sha1', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Certificate fingerprint using the SHA1 digest of DER-encoded version\nof certificate offered by the server. For consistency with other hash values,\nthis value should be formatted as an uppercase hash.', - example: '9E393D93138888D288266C2D915214D1D1CCEB2A', - default_field: false, - }, - { - name: 'server.hash.sha256', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Certificate fingerprint using the SHA256 digest of DER-encoded\nversion of certificate offered by the server. For consistency with other hash\nvalues, this value should be formatted as an uppercase hash.', - example: '0687F666A054EF17A08E2F2162EAB4CBC0D265E1D7875BE74BF3C712CA92DAF0', - default_field: false, - }, - { - name: 'server.issuer', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Subject of the issuer of the x.509 certificate presented by the\nserver.', - example: 'CN=MyDomain Root CA, OU=Infrastructure Team, DC=mydomain, DC=com', - default_field: false, - }, - { - name: 'server.ja3s', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A hash that identifies servers based on how they perform an SSL/TLS\nhandshake.', - example: '394441ab65754e2207b1e1b457b3641d', - default_field: false, - }, - { - name: 'server.not_after', - level: 'extended', - type: 'date', - description: - 'Timestamp indicating when server certificate is no longer considered\nvalid.', - example: '2021-01-01T00:00:00.000Z', - default_field: false, - }, - { - name: 'server.not_before', - level: 'extended', - type: 'date', - description: 'Timestamp indicating when server certificate is first considered\nvalid.', - example: '1970-01-01T00:00:00.000Z', - default_field: false, - }, - { - name: 'server.subject', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Subject of the x.509 certificate presented by the server.', - example: 'CN=www.mydomain.com, OU=Infrastructure Team, DC=mydomain, DC=com', - default_field: false, - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Numeric part of the version parsed from the original string.', - example: '1.2', - default_field: false, - }, - { - name: 'version_protocol', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Normalized lowercase protocol name parsed from original string.', - example: 'tls', - default_field: false, - }, - ], - }, - { - name: 'tracing', - title: 'Tracing', - group: 2, - description: - 'Distributed tracing makes it possible to analyze performance throughout\na microservice architecture all in one view. This is accomplished by tracing\nall of the requests - from the initial web request in the front-end service\n- to queries made through multiple back-end services.', - type: 'group', - fields: [ - { - name: 'trace.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique identifier of the trace.\n\nA trace groups multiple events like transactions that belong together. For\nexample, a user request handled by multiple inter-connected services.', - example: '4bf92f3577b34da6a3ce929d0e0e4736', - }, - { - name: 'transaction.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique identifier of the transaction.\n\nA transaction is the highest level of work measured within a service, such\nas a request to a server.', - example: '00f067aa0ba902b7', - }, - ], - }, - { - name: 'url', - title: 'URL', - group: 2, - description: - 'URL fields provide support for complete or partial URLs, and supports\nthe breaking down into scheme, domain, path, and so on.', - type: 'group', - fields: [ - { - name: 'domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Domain of the url, such as "www.elastic.co".\n\nIn some cases a URL may refer to an IP and/or port directly, without a domain\nname. In this case, the IP address would go to the `domain` field.', - example: 'www.elastic.co', - }, - { - name: 'extension', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The field contains the file extension from the original request\nurl.\n\nThe file extension is only set if it exists, as not every url has a file extension.\n\nThe leading period must not be included. For example, the value must be "png",\nnot ".png".', - example: 'png', - }, - { - name: 'fragment', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Portion of the url after the `#`, such as "top".\n\nThe `#` is not part of the fragment.', - }, - { - name: 'full', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: - 'If full URLs are important to your use case, they should be stored\nin `url.full`, whether this field is reconstructed or present in the event\nsource.', - example: 'https://www.elastic.co:443/search?q=elasticsearch#top', - }, - { - name: 'original', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: - 'Unmodified original url as seen in the event source.\n\nNote that in network monitoring, the observed URL may be a full URL, whereas\nin access logs, the URL is often just represented as a path.\n\nThis field is meant to represent the URL as it was observed, complete or not.', - example: - 'https://www.elastic.co:443/search?q=elasticsearch#top or /search?q=elasticsearch', - }, - { - name: 'password', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Password of the request.', - }, - { - name: 'path', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Path of the request, such as "/search".', - }, - { - name: 'port', - level: 'extended', - type: 'long', - format: 'string', - description: 'Port of the request, such as 443.', - example: 443, - }, - { - name: 'query', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The query field describes the query string of the request, such\nas "q=elasticsearch".\n\nThe `?` is excluded from the query string. If a URL contains no `?`, there\nis no query field. If there is a `?` but no query, the query field exists\nwith an empty string. The `exists` query can be used to differentiate between\nthe two cases.', - }, - { - name: 'registered_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The highest registered url domain, stripped of the subdomain.\n\nFor example, the registered domain for "foo.google.com" is "google.com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last two labels will not work well for TLDs such as "co.uk".', - example: 'google.com', - }, - { - name: 'scheme', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Scheme of the request, such as "https".\n\nNote: The `:` is not part of the scheme.', - example: 'https', - }, - { - name: 'top_level_domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The effective top level domain (eTLD), also known as the domain\nsuffix, is the last part of the domain name. For example, the top level domain\nfor google.com is "com".\n\nThis value can be determined precisely with a list like the public suffix\nlist (http://publicsuffix.org). Trying to approximate this by simply taking\nthe last label will not work well for effective TLDs such as "co.uk".', - example: 'co.uk', - }, - { - name: 'username', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Username of the request.', - }, - ], - }, - { - name: 'user', - title: 'User', - group: 2, - description: - 'The user fields describe information about the user that is relevant\nto the event.\n\nFields can have one entry or multiple entries. If a user has more than one id,\nprovide an array that includes all of them.', - type: 'group', - fields: [ - { - name: 'domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the user is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'email', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'User email address.', - }, - { - name: 'full_name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'group.domain', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', - }, - { - name: 'group.id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'group.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the group.', - }, - { - name: 'hash', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - ignore_above: 1024, - description: 'Unique identifiers of the user.', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'user_agent', - title: 'User agent', - group: 2, - description: - 'The user_agent fields normally come from a browser request.\n\nThey often show up in web service logs coming from the parsed user agent string.', - type: 'group', - fields: [ - { - name: 'device.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the device.', - example: 'iPhone', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Name of the user agent.', - example: 'Safari', - }, - { - name: 'original', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: 'Unparsed user_agent string.', - example: - 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15\n(KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1', - }, - { - name: 'os.family', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'OS family (such as redhat, debian, freebsd, windows).', - example: 'debian', - }, - { - name: 'os.full', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, including the version or code name.', - example: 'Mac OS Mojave', - }, - { - name: 'os.kernel', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system kernel version as a raw string.', - example: '4.4.0-112-generic', - }, - { - name: 'os.name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - default_field: false, - }, - ], - description: 'Operating system name, without the version.', - example: 'Mac OS X', - }, - { - name: 'os.platform', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'os.version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Operating system version as a raw string.', - example: '10.14.1', - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Version of the user agent.', - example: 12, - }, - ], - }, - { - name: 'vlan', - title: 'VLAN', - group: 2, - description: - 'The VLAN fields are used to identify 802.1q tag(s) of a packet,\nas well as ingress and egress VLAN associations of an observer in relation to\na specific packet or connection.\n\nNetwork.vlan fields are used to record a single VLAN tag, or the outer tag in\nthe case of q-in-q encapsulations, for a packet or connection as observed, typically\nprovided by a network sensor (e.g. Zeek, Wireshark) passively reporting on traffic.\n\nNetwork.inner VLAN fields are used to report inner q-in-q 802.1q tags (multiple\n802.1q encapsulations) as observed, typically provided by a network sensor (e.g.\nZeek, Wireshark) passively reporting on traffic. Network.inner VLAN fields should\nonly be used in addition to network.vlan fields to indicate q-in-q tagging.\n\nObserver.ingress and observer.egress VLAN values are used to record observer\nspecific information when observer events contain discrete ingress and egress\nVLAN information, typically provided by firewalls, routers, or load balancers.', - type: 'group', - fields: [ - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'VLAN ID as reported by the observer.', - example: 10, - default_field: false, - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'Optional VLAN name as reported by the observer.', - example: 'outside', - default_field: false, - }, - ], - }, - { - name: 'vulnerability', - title: 'Vulnerability', - group: 2, - description: - 'The vulnerability fields describe information about a vulnerability\nthat is relevant to an event.', - type: 'group', - fields: [ - { - name: 'category', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The type of system or architecture that the vulnerability affects.\nThese may be platform-specific (for example, Debian or SUSE) or general (for\nexample, Database or Firewall). For example (https://qualysguard.qualys.com/qwebhelp/fo_portal/knowledgebase/vulnerability_categories.htm[Qualys\nvulnerability categories])\n\nThis field must be an array.', - example: '["Firewall"]', - default_field: false, - }, - { - name: 'classification', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The classification of the vulnerability scoring system. For example\n(https://www.first.org/cvss/)', - example: 'CVSS', - default_field: false, - }, - { - name: 'description', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - multi_fields: [ - { - name: 'text', - type: 'text', - norms: false, - }, - ], - description: - 'The description of the vulnerability that provides additional context\nof the vulnerability. For example (https://cve.mitre.org/about/faqs.html#cve_entry_descriptions_created[Common\nVulnerabilities and Exposure CVE description])', - example: 'In macOS before 2.12.6, there is a vulnerability in the RPC...', - default_field: false, - }, - { - name: 'enumeration', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The type of identifier used for this vulnerability. For example\n(https://cve.mitre.org/about/)', - example: 'CVE', - default_field: false, - }, - { - name: 'id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The identification (ID) is the number portion of a vulnerability\nentry. It includes a unique identification number for the vulnerability. For\nexample (https://cve.mitre.org/about/faqs.html#what_is_cve_id)[Common Vulnerabilities\nand Exposure CVE ID]', - example: 'CVE-2019-00001', - default_field: false, - }, - { - name: 'reference', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'A resource that provides additional information, context, and mitigations\nfor the identified vulnerability.', - example: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-6111', - default_field: false, - }, - { - name: 'report_id', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The report or scan identification number.', - example: 20191018.0001, - default_field: false, - }, - { - name: 'scanner.vendor', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: 'The name of the vulnerability scanner vendor.', - example: 'Tenable', - default_field: false, - }, - { - name: 'score.base', - level: 'extended', - type: 'float', - description: - 'Scores can range from 0.0 to 10.0, with 10.0 being the most severe.\n\nBase scores cover an assessment for exploitability metrics (attack vector,\ncomplexity, privileges, and user interaction), impact metrics (confidentiality,\nintegrity, and availability), and scope. For example (https://www.first.org/cvss/specification-document)', - example: 5.5, - default_field: false, - }, - { - name: 'score.environmental', - level: 'extended', - type: 'float', - description: - 'Scores can range from 0.0 to 10.0, with 10.0 being the most severe.\n\nEnvironmental scores cover an assessment for any modified Base metrics, confidentiality,\nintegrity, and availability requirements. For example (https://www.first.org/cvss/specification-document)', - example: 5.5, - default_field: false, - }, - { - name: 'score.temporal', - level: 'extended', - type: 'float', - description: - 'Scores can range from 0.0 to 10.0, with 10.0 being the most severe.\n\nTemporal scores cover an assessment for code maturity, remediation level,\nand confidence. For example (https://www.first.org/cvss/specification-document)', - default_field: false, - }, - { - name: 'score.version', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The National Vulnerability Database (NVD) provides qualitative\nseverity rankings of "Low", "Medium", and "High" for CVSS v2.0 base score\nranges in addition to the severity ratings for CVSS v3.0 as they are defined\nin the CVSS v3.0 specification.\n\nCVSS is owned and managed by FIRST.Org, Inc. (FIRST), a US-based non-profit\norganization, whose mission is to help computer security incident response\nteams across the world. For example (https://nvd.nist.gov/vuln-metrics/cvss)', - example: 2, - default_field: false, - }, - { - name: 'severity', - level: 'extended', - type: 'keyword', - ignore_above: 1024, - description: - 'The severity of the vulnerability can help with metrics and internal\nprioritization regarding remediation. For example (https://nvd.nist.gov/vuln-metrics/cvss)', - example: 'Critical', - default_field: false, - }, - ], - }, - ], - }, - { - key: 'beat', - anchor: 'beat-common', - title: 'Beat', - description: 'Contains common beat fields available in all event types.\n', - fields: [ - { - name: 'agent.hostname', - type: 'keyword', - description: 'Hostname of the agent.', - }, - { - name: 'beat.timezone', - type: 'alias', - path: 'event.timezone', - migration: true, - }, - { - name: 'fields', - type: 'object', - object_type: 'keyword', - description: 'Contains user configurable fields.\n', - }, - { - name: 'beat.name', - type: 'alias', - path: 'host.name', - migration: true, - }, - { - name: 'beat.hostname', - type: 'alias', - path: 'agent.hostname', - migration: true, - }, - { - name: 'timeseries.instance', - type: 'keyword', - description: 'Time series instance id', - }, - ], - }, - { - key: 'cloud', - title: 'Cloud provider metadata', - description: 'Metadata from cloud providers added by the add_cloud_metadata processor.\n', - fields: [ - { - name: 'cloud.project.id', - example: 'project-x', - description: 'Name of the project in Google Cloud.\n', - }, - { - name: 'cloud.image.id', - example: 'ami-abcd1234', - description: 'Image ID for the cloud instance.\n', - }, - { - name: 'meta.cloud.provider', - type: 'alias', - path: 'cloud.provider', - migration: true, - }, - { - name: 'meta.cloud.instance_id', - type: 'alias', - path: 'cloud.instance.id', - migration: true, - }, - { - name: 'meta.cloud.instance_name', - type: 'alias', - path: 'cloud.instance.name', - migration: true, - }, - { - name: 'meta.cloud.machine_type', - type: 'alias', - path: 'cloud.machine.type', - migration: true, - }, - { - name: 'meta.cloud.availability_zone', - type: 'alias', - path: 'cloud.availability_zone', - migration: true, - }, - { - name: 'meta.cloud.project_id', - type: 'alias', - path: 'cloud.project.id', - migration: true, - }, - { - name: 'meta.cloud.region', - type: 'alias', - path: 'cloud.region', - migration: true, - }, - ], - }, - { - key: 'docker', - title: 'Docker', - description: 'Docker stats collected from Docker.\n', - short_config: false, - anchor: 'docker-processor', - fields: [ - { - name: 'docker', - type: 'group', - fields: [ - { - name: 'container.id', - type: 'alias', - path: 'container.id', - migration: true, - }, - { - name: 'container.image', - type: 'alias', - path: 'container.image.name', - migration: true, - }, - { - name: 'container.name', - type: 'alias', - path: 'container.name', - migration: true, - }, - { - name: 'container.labels', - type: 'object', - object_type: 'keyword', - description: 'Image labels.\n', - }, - ], - }, - ], - }, - { - key: 'host', - title: 'Host', - description: 'Info collected for the host machine.\n', - anchor: 'host-processor', - fields: [ - { - name: 'host', - type: 'group', - fields: [ - { - name: 'containerized', - type: 'boolean', - description: 'If the host is a container.\n', - }, - { - name: 'os.build', - type: 'keyword', - example: '18D109', - description: 'OS build information.\n', - }, - { - name: 'os.codename', - type: 'keyword', - example: 'stretch', - description: 'OS codename, if any.\n', - }, - ], - }, - ], - }, - { - key: 'kubernetes', - title: 'Kubernetes', - description: 'Kubernetes metadata added by the kubernetes processor\n', - short_config: false, - anchor: 'kubernetes-processor', - fields: [ - { - name: 'kubernetes', - type: 'group', - fields: [ - { - name: 'pod.name', - type: 'keyword', - description: 'Kubernetes pod name\n', - }, - { - name: 'pod.uid', - type: 'keyword', - description: 'Kubernetes Pod UID\n', - }, - { - name: 'namespace', - type: 'keyword', - description: 'Kubernetes namespace\n', - }, - { - name: 'node.name', - type: 'keyword', - description: 'Kubernetes node name\n', - }, - { - name: 'labels.*', - type: 'object', - object_type: 'keyword', - object_type_mapping_type: '*', - description: 'Kubernetes labels map\n', - }, - { - name: 'annotations.*', - type: 'object', - object_type: 'keyword', - object_type_mapping_type: '*', - description: 'Kubernetes annotations map\n', - }, - { - name: 'replicaset.name', - type: 'keyword', - description: 'Kubernetes replicaset name\n', - }, - { - name: 'deployment.name', - type: 'keyword', - description: 'Kubernetes deployment name\n', - }, - { - name: 'statefulset.name', - type: 'keyword', - description: 'Kubernetes statefulset name\n', - }, - { - name: 'container.name', - type: 'keyword', - description: 'Kubernetes container name\n', - }, - { - name: 'container.image', - type: 'keyword', - description: 'Kubernetes container image\n', - }, - ], - }, - ], - }, - { - key: 'process', - title: 'Process', - description: 'Process metadata fields\n', - fields: [ - { - name: 'process', - type: 'group', - fields: [ - { - name: 'exe', - type: 'alias', - path: 'process.executable', - migration: true, - }, - ], - }, - ], - }, - { - key: 'jolokia-autodiscover', - title: 'Jolokia Discovery autodiscover provider', - description: 'Metadata from Jolokia Discovery added by the jolokia provider.\n', - fields: [ - { - name: 'jolokia.agent.version', - type: 'keyword', - description: 'Version number of jolokia agent.\n', - }, - { - name: 'jolokia.agent.id', - type: 'keyword', - description: - 'Each agent has a unique id which can be either provided during startup of the agent in form of a configuration parameter or being autodetected. If autodected, the id has several parts: The IP, the process id, hashcode of the agent and its type.\n', - }, - { - name: 'jolokia.server.product', - type: 'keyword', - description: 'The container product if detected.\n', - }, - { - name: 'jolokia.server.version', - type: 'keyword', - description: "The container's version (if detected).\n", - }, - { - name: 'jolokia.server.vendor', - type: 'keyword', - description: 'The vendor of the container the agent is running in.\n', - }, - { - name: 'jolokia.url', - type: 'keyword', - description: 'The URL how this agent can be contacted.\n', - }, - { - name: 'jolokia.secured', - type: 'boolean', - description: 'Whether the agent was configured for authentication or not.\n', - }, - ], - }, - { - key: 'common', - title: 'Common', - description: - 'These fields contain data about the environment in which the transaction or flow was captured.\n', - fields: [ - { - name: 'type', - description: - 'The type of the transaction (for example, HTTP, MySQL, Redis, or RUM) or "flow" in case of flows.\n', - required: true, - }, - { - name: 'server.process.name', - description: 'The name of the process that served the transaction.\n', - }, - { - name: 'server.process.args', - description: 'The command-line of the process that served the transaction.\n', - }, - { - name: 'server.process.executable', - description: 'Absolute path to the server process executable.\n', - }, - { - name: 'server.process.working_directory', - description: 'The working directory of the server process.\n', - }, - { - name: 'server.process.start', - description: 'The time the server process started.\n', - }, - { - name: 'client.process.name', - description: 'The name of the process that initiated the transaction.\n', - }, - { - name: 'client.process.args', - description: 'The command-line of the process that initiated the transaction.\n', - }, - { - name: 'client.process.executable', - description: 'Absolute path to the client process executable.\n', - }, - { - name: 'client.process.working_directory', - description: 'The working directory of the client process.\n', - }, - { - name: 'client.process.start', - description: 'The time the client process started.\n', - }, - { - name: 'real_ip', - type: 'alias', - path: 'network.forwarded_ip', - migration: true, - description: - 'If the server initiating the transaction is a proxy, this field contains the original client IP address. For HTTP, for example, the IP address extracted from a configurable HTTP header, by default `X-Forwarded-For`.\nUnless this field is disabled, it always has a value, and it matches the `client_ip` for non proxy clients.\n', - }, - { - name: 'transport', - type: 'alias', - path: 'network.transport', - migration: true, - description: - 'The transport protocol used for the transaction. If not specified, then tcp is assumed.\n', - }, - ], - }, - { - key: 'flows_event', - title: 'Flow Event', - description: 'These fields contain data about the flow itself.\n', - fields: [ - { - name: 'flow.final', - type: 'boolean', - description: - 'Indicates if event is last event in flow. If final is false, the event reports an intermediate flow state only.\n', - }, - { - name: 'flow.id', - description: 'Internal flow ID based on connection meta data and address.\n', - }, - { - name: 'flow.vlan', - type: 'long', - description: - "VLAN identifier from the 802.1q frame. In case of a multi-tagged frame this field will be an array with the outer tag's VLAN identifier listed first.\n", - }, - { - name: 'flow_id', - type: 'alias', - path: 'flow.id', - migration: true, - }, - { - name: 'final', - type: 'alias', - path: 'flow.final', - migration: true, - }, - { - name: 'vlan', - type: 'alias', - path: 'flow.vlan', - migration: true, - }, - { - name: 'source.stats.net_bytes_total', - type: 'alias', - path: 'source.bytes', - migration: true, - }, - { - name: 'source.stats.net_packets_total', - type: 'alias', - path: 'source.packets', - migration: true, - }, - { - name: 'dest.stats.net_bytes_total', - type: 'alias', - path: 'destination.bytes', - migration: true, - }, - { - name: 'dest.stats.net_packets_total', - type: 'alias', - path: 'destination.packets', - migration: true, - }, - ], - }, - { - key: 'trans_event', - title: 'Transaction Event', - description: 'These fields contain data about the transaction itself.\n', - fields: [ - { - name: 'status', - description: - 'The high level status of the transaction. The way to compute this value depends on the protocol, but the result has a meaning independent of the protocol.\n', - required: true, - possible_values: ['OK', 'Error', 'Server Error', 'Client Error'], - }, - { - name: 'method', - description: - 'The command/verb/method of the transaction. For HTTP, this is the method name (GET, POST, PUT, and so on), for SQL this is the verb (SELECT, UPDATE, DELETE, and so on).\n', - }, - { - name: 'resource', - description: - 'The logical resource that this transaction refers to. For HTTP, this is the URL path up to the last slash (/). For example, if the URL is `/users/1`, the resource is `/users`. For databases, the resource is typically the table name. The field is not filled for all transaction types.\n', - }, - { - name: 'path', - required: true, - description: - 'The path the transaction refers to. For HTTP, this is the URL. For SQL databases, this is the table name. For key-value stores, this is the key.\n', - }, - { - name: 'query', - type: 'keyword', - description: - 'The query in a human readable format. For HTTP, it will typically be something like `GET /users/_search?name=test`. For MySQL, it is something like `SELECT id from users where name=test`.\n', - }, - { - name: 'params', - type: 'text', - description: - 'The request parameters. For HTTP, these are the POST or GET parameters. For Thrift-RPC, these are the parameters from the request.\n', - }, - { - name: 'notes', - type: 'alias', - path: 'error.message', - description: - 'Messages from Packetbeat itself. This field usually contains error messages for interpreting the raw data. This information can be helpful for troubleshooting.\n', - }, - ], - }, - { - key: 'raw', - title: 'Raw', - description: 'These fields contain the raw transaction data.', - fields: [ - { - name: 'request', - type: 'text', - description: - 'For text protocols, this is the request as seen on the wire (application layer only). For binary protocols this is our representation of the request.\n', - }, - { - name: 'response', - type: 'text', - description: - 'For text protocols, this is the response as seen on the wire (application layer only). For binary protocols this is our representation of the request.\n', - }, - ], - }, - { - key: 'trans_measurements', - title: 'Measurements (Transactions)', - description: 'These fields contain measurements related to the transaction.\n', - fields: [ - { - name: 'bytes_in', - type: 'alias', - path: 'source.bytes', - description: - 'The number of bytes of the request. Note that this size is the application layer message length, without the length of the IP or TCP headers.\n', - }, - { - name: 'bytes_out', - type: 'alias', - path: 'destination.bytes', - description: - 'The number of bytes of the response. Note that this size is the application layer message length, without the length of the IP or TCP headers.\n', - }, - ], - }, - { - key: 'amqp', - title: 'AMQP', - description: 'AMQP specific event fields.', - fields: [ - { - name: 'amqp', - type: 'group', - fields: [ - { - name: 'reply-code', - type: 'long', - description: 'AMQP reply code to an error, similar to http reply-code\n', - example: 404, - }, - { - name: 'reply-text', - type: 'keyword', - description: 'Text explaining the error.\n', - }, - { - name: 'class-id', - type: 'long', - description: 'Failing method class.\n', - }, - { - name: 'method-id', - type: 'long', - description: 'Failing method ID.\n', - }, - { - name: 'exchange', - type: 'keyword', - description: 'Name of the exchange.\n', - }, - { - name: 'exchange-type', - type: 'keyword', - description: 'Exchange type.\n', - example: 'fanout', - }, - { - name: 'passive', - type: 'boolean', - description: 'If set, do not create exchange/queue.\n', - }, - { - name: 'durable', - type: 'boolean', - description: 'If set, request a durable exchange/queue.\n', - }, - { - name: 'exclusive', - type: 'boolean', - description: 'If set, request an exclusive queue.\n', - }, - { - name: 'auto-delete', - type: 'boolean', - description: 'If set, auto-delete queue when unused.\n', - }, - { - name: 'no-wait', - type: 'boolean', - description: 'If set, the server will not respond to the method.\n', - }, - { - name: 'consumer-tag', - description: 'Identifier for the consumer, valid within the current channel.\n', - }, - { - name: 'delivery-tag', - type: 'long', - description: 'The server-assigned and channel-specific delivery tag.\n', - }, - { - name: 'message-count', - type: 'long', - description: - 'The number of messages in the queue, which will be zero for newly-declared queues.\n', - }, - { - name: 'consumer-count', - type: 'long', - description: 'The number of consumers of a queue.\n', - }, - { - name: 'routing-key', - type: 'keyword', - description: 'Message routing key.\n', - }, - { - name: 'no-ack', - type: 'boolean', - description: 'If set, the server does not expect acknowledgements for messages.\n', - }, - { - name: 'no-local', - type: 'boolean', - description: - 'If set, the server will not send messages to the connection that published them.\n', - }, - { - name: 'if-unused', - type: 'boolean', - description: 'Delete only if unused.\n', - }, - { - name: 'if-empty', - type: 'boolean', - description: 'Delete only if empty.\n', - }, - { - name: 'queue', - type: 'keyword', - description: 'The queue name identifies the queue within the vhost.\n', - }, - { - name: 'redelivered', - type: 'boolean', - description: - 'Indicates that the message has been previously delivered to this or another client.\n', - }, - { - name: 'multiple', - type: 'boolean', - description: 'Acknowledge multiple messages.\n', - }, - { - name: 'arguments', - type: 'object', - description: - 'Optional additional arguments passed to some methods. Can be of various types.\n', - }, - { - name: 'mandatory', - type: 'boolean', - description: 'Indicates mandatory routing.\n', - }, - { - name: 'immediate', - type: 'boolean', - description: 'Request immediate delivery.\n', - }, - { - name: 'content-type', - type: 'keyword', - description: 'MIME content type.\n', - example: 'text/plain', - }, - { - name: 'content-encoding', - type: 'keyword', - description: 'MIME content encoding.\n', - }, - { - name: 'headers', - type: 'object', - object_type: 'keyword', - description: 'Message header field table.\n', - }, - { - name: 'delivery-mode', - type: 'keyword', - description: 'Non-persistent (1) or persistent (2).\n', - }, - { - name: 'priority', - type: 'long', - description: 'Message priority, 0 to 9.\n', - }, - { - name: 'correlation-id', - type: 'keyword', - description: 'Application correlation identifier.\n', - }, - { - name: 'reply-to', - type: 'keyword', - description: 'Address to reply to.\n', - }, - { - name: 'expiration', - type: 'keyword', - description: 'Message expiration specification.\n', - }, - { - name: 'message-id', - type: 'keyword', - description: 'Application message identifier.\n', - }, - { - name: 'timestamp', - type: 'keyword', - description: 'Message timestamp.\n', - }, - { - name: 'type', - type: 'keyword', - description: 'Message type name.\n', - }, - { - name: 'user-id', - type: 'keyword', - description: 'Creating user id.\n', - }, - { - name: 'app-id', - type: 'keyword', - description: 'Creating application id.\n', - }, - ], - }, - ], - }, - { - key: 'cassandra', - title: 'Cassandra', - description: 'Cassandra v4/3 specific event fields.', - fields: [ - { - name: 'no_request', - type: 'alias', - path: 'cassandra.no_request', - migration: true, - }, - { - name: 'cassandra', - type: 'group', - description: 'Information about the Cassandra request and response.', - fields: [ - { - name: 'no_request', - type: 'boolean', - description: 'Indicates that there is no request because this is a PUSH message.\n', - }, - { - name: 'request', - type: 'group', - description: 'Cassandra request.', - fields: [ - { - name: 'headers', - type: 'group', - description: 'Cassandra request headers.', - fields: [ - { - name: 'version', - type: 'long', - description: 'The version of the protocol.', - }, - { - name: 'flags', - type: 'keyword', - description: 'Flags applying to this frame.', - }, - { - name: 'stream', - type: 'keyword', - description: - 'A frame has a stream id. If a client sends a request message with the stream id X, it is guaranteed that the stream id of the response to that message will be X.', - }, - { - name: 'op', - type: 'keyword', - description: 'An operation type that distinguishes the actual message.', - }, - { - name: 'length', - type: 'long', - description: - 'A integer representing the length of the body of the frame (a frame is limited to 256MB in length).', - }, - ], - }, - { - name: 'query', - type: 'keyword', - description: 'The CQL query which client send to cassandra.', - }, - ], - }, - { - name: 'response', - type: 'group', - description: 'Cassandra response.', - fields: [ - { - name: 'headers', - type: 'group', - description: - "Cassandra response headers, the structure is as same as request's header.", - fields: [ - { - name: 'version', - type: 'long', - description: 'The version of the protocol.', - }, - { - name: 'flags', - type: 'keyword', - description: 'Flags applying to this frame.', - }, - { - name: 'stream', - type: 'keyword', - description: - 'A frame has a stream id. If a client sends a request message with the stream id X, it is guaranteed that the stream id of the response to that message will be X.', - }, - { - name: 'op', - type: 'keyword', - description: 'An operation type that distinguishes the actual message.', - }, - { - name: 'length', - type: 'long', - description: - 'A integer representing the length of the body of the frame (a frame is limited to 256MB in length).', - }, - ], - }, - { - name: 'result', - type: 'group', - description: 'Details about the returned result.', - fields: [ - { - name: 'type', - type: 'keyword', - description: 'Cassandra result type.', - }, - { - name: 'rows', - type: 'group', - description: 'Details about the rows.', - fields: [ - { - name: 'num_rows', - type: 'long', - description: 'Representing the number of rows present in this result.', - }, - { - name: 'meta', - type: 'group', - description: 'Composed of result metadata.', - fields: [ - { - name: 'keyspace', - type: 'keyword', - description: - 'Only present after set Global_tables_spec, the keyspace name.', - }, - { - name: 'table', - type: 'keyword', - description: - 'Only present after set Global_tables_spec, the table name.', - }, - { - name: 'flags', - type: 'keyword', - description: - 'Provides information on the formatting of the remaining information.', - }, - { - name: 'col_count', - type: 'long', - description: - 'Representing the number of columns selected by the query that produced this result.', - }, - { - name: 'pkey_columns', - type: 'long', - description: 'Representing the PK columns index and counts.', - }, - { - name: 'paging_state', - type: 'keyword', - description: - 'The paging_state is a bytes value that should be used in QUERY/EXECUTE to continue paging and retrieve the remainder of the result for this query.', - }, - ], - }, - ], - }, - { - name: 'keyspace', - type: 'keyword', - description: 'Indicating the name of the keyspace that has been set.', - }, - { - name: 'schema_change', - type: 'group', - description: 'The result to a schema_change message.', - fields: [ - { - name: 'change', - type: 'keyword', - description: 'Representing the type of changed involved.', - }, - { - name: 'keyspace', - type: 'keyword', - description: 'This describes which keyspace has changed.', - }, - { - name: 'table', - type: 'keyword', - description: 'This describes which table has changed.', - }, - { - name: 'object', - type: 'keyword', - description: - 'This describes the name of said affected object (either the table, user type, function, or aggregate name).', - }, - { - name: 'target', - type: 'keyword', - description: - 'Target could be "FUNCTION" or "AGGREGATE", multiple arguments.', - }, - { - name: 'name', - type: 'keyword', - description: 'The function/aggregate name.', - }, - { - name: 'args', - type: 'keyword', - description: 'One string for each argument type (as CQL type).', - }, - ], - }, - { - name: 'prepared', - type: 'group', - description: 'The result to a PREPARE message.', - fields: [ - { - name: 'prepared_id', - type: 'keyword', - description: 'Representing the prepared query ID.', - }, - { - name: 'req_meta', - type: 'group', - description: 'This describes the request metadata.', - fields: [ - { - name: 'keyspace', - type: 'keyword', - description: - 'Only present after set Global_tables_spec, the keyspace name.', - }, - { - name: 'table', - type: 'keyword', - description: - 'Only present after set Global_tables_spec, the table name.', - }, - { - name: 'flags', - type: 'keyword', - description: - 'Provides information on the formatting of the remaining information.', - }, - { - name: 'col_count', - type: 'long', - description: - 'Representing the number of columns selected by the query that produced this result.', - }, - { - name: 'pkey_columns', - type: 'long', - description: 'Representing the PK columns index and counts.', - }, - { - name: 'paging_state', - type: 'keyword', - description: - 'The paging_state is a bytes value that should be used in QUERY/EXECUTE to continue paging and retrieve the remainder of the result for this query.', - }, - ], - }, - { - name: 'resp_meta', - type: 'group', - description: 'This describes the metadata for the result set.', - fields: [ - { - name: 'keyspace', - type: 'keyword', - description: - 'Only present after set Global_tables_spec, the keyspace name.', - }, - { - name: 'table', - type: 'keyword', - description: - 'Only present after set Global_tables_spec, the table name.', - }, - { - name: 'flags', - type: 'keyword', - description: - 'Provides information on the formatting of the remaining information.', - }, - { - name: 'col_count', - type: 'long', - description: - 'Representing the number of columns selected by the query that produced this result.', - }, - { - name: 'pkey_columns', - type: 'long', - description: 'Representing the PK columns index and counts.', - }, - { - name: 'paging_state', - type: 'keyword', - description: - 'The paging_state is a bytes value that should be used in QUERY/EXECUTE to continue paging and retrieve the remainder of the result for this query.', - }, - ], - }, - ], - }, - ], - }, - { - name: 'supported', - type: 'object', - object_type: 'keyword', - description: - 'Indicates which startup options are supported by the server. This message comes as a response to an OPTIONS message.', - }, - { - name: 'authentication', - type: 'group', - description: - 'Indicates that the server requires authentication, and which authentication mechanism to use.', - fields: [ - { - name: 'class', - type: 'keyword', - description: 'Indicates the full class name of the IAuthenticator in use', - }, - ], - }, - { - name: 'warnings', - type: 'keyword', - description: 'The text of the warnings, only occur when Warning flag was set.', - }, - { - name: 'event', - type: 'group', - description: - 'Event pushed by the server. A client will only receive events for the types it has REGISTERed to.', - fields: [ - { - name: 'type', - type: 'keyword', - description: 'Representing the event type.', - }, - { - name: 'change', - type: 'keyword', - description: - 'The message corresponding respectively to the type of change followed by the address of the new/removed node.', - }, - { - name: 'host', - type: 'keyword', - description: 'Representing the node ip.', - }, - { - name: 'port', - type: 'long', - description: 'Representing the node port.', - }, - { - name: 'schema_change', - type: 'group', - description: 'The events details related to schema change.', - fields: [ - { - name: 'change', - type: 'keyword', - description: 'Representing the type of changed involved.', - }, - { - name: 'keyspace', - type: 'keyword', - description: 'This describes which keyspace has changed.', - }, - { - name: 'table', - type: 'keyword', - description: 'This describes which table has changed.', - }, - { - name: 'object', - type: 'keyword', - description: - 'This describes the name of said affected object (either the table, user type, function, or aggregate name).', - }, - { - name: 'target', - type: 'keyword', - description: - 'Target could be "FUNCTION" or "AGGREGATE", multiple arguments.', - }, - { - name: 'name', - type: 'keyword', - description: 'The function/aggregate name.', - }, - { - name: 'args', - type: 'keyword', - description: 'One string for each argument type (as CQL type).', - }, - ], - }, - ], - }, - { - name: 'error', - type: 'group', - description: - 'Indicates an error processing a request. The body of the message will be an error code followed by a error message. Then, depending on the exception, more content may follow.', - fields: [ - { - name: 'code', - type: 'long', - description: 'The error code of the Cassandra response.', - }, - { - name: 'msg', - type: 'keyword', - description: 'The error message of the Cassandra response.', - }, - { - name: 'type', - type: 'keyword', - description: 'The error type of the Cassandra response.', - }, - { - name: 'details', - type: 'group', - description: 'The details of the error.', - fields: [ - { - name: 'read_consistency', - type: 'keyword', - description: - 'Representing the consistency level of the query that triggered the exception.', - }, - { - name: 'required', - type: 'long', - description: - 'Representing the number of nodes that should be alive to respect consistency level.', - }, - { - name: 'alive', - type: 'long', - description: - 'Representing the number of replicas that were known to be alive when the request had been processed (since an unavailable exception has been triggered).', - }, - { - name: 'received', - type: 'long', - description: - 'Representing the number of nodes having acknowledged the request.', - }, - { - name: 'blockfor', - type: 'long', - description: - 'Representing the number of replicas whose acknowledgement is required to achieve consistency level.', - }, - { - name: 'write_type', - type: 'keyword', - description: 'Describe the type of the write that timed out.', - }, - { - name: 'data_present', - type: 'boolean', - description: 'It means the replica that was asked for data had responded.', - }, - { - name: 'keyspace', - type: 'keyword', - description: 'The keyspace of the failed function.', - }, - { - name: 'table', - type: 'keyword', - description: 'The keyspace of the failed function.', - }, - { - name: 'stmt_id', - type: 'keyword', - description: 'Representing the unknown ID.', - }, - { - name: 'num_failures', - type: 'keyword', - description: - 'Representing the number of nodes that experience a failure while executing the request.', - }, - { - name: 'function', - type: 'keyword', - description: 'The name of the failed function.', - }, - { - name: 'arg_types', - type: 'keyword', - description: - 'One string for each argument type (as CQL type) of the failed function.', - }, - ], - }, - ], - }, - ], - }, - ], - }, - ], - }, - { - key: 'dhcpv4', - title: 'DHCPv4', - description: 'DHCPv4 event fields', - fields: [ - { - name: 'dhcpv4', - type: 'group', - fields: [ - { - name: 'transaction_id', - type: 'keyword', - description: - 'Transaction ID, a random number chosen by the\nclient, used by the client and server to associate\nmessages and responses between a client and a\nserver.\n', - }, - { - name: 'seconds', - type: 'long', - description: - 'Number of seconds elapsed since client began address acquisition or\nrenewal process.\n', - }, - { - name: 'flags', - type: 'keyword', - description: - 'Flags are set by the client to indicate how the DHCP server should\nits reply -- either unicast or broadcast.\n', - }, - { - name: 'client_ip', - type: 'ip', - description: 'The current IP address of the client.', - }, - { - name: 'assigned_ip', - type: 'ip', - description: - 'The IP address that the DHCP server is assigning to the client.\nThis field is also known as "your" IP address.\n', - }, - { - name: 'server_ip', - type: 'ip', - description: - 'The IP address of the DHCP server that the client should use for the\nnext step in the bootstrap process.\n', - }, - { - name: 'relay_ip', - type: 'ip', - description: - 'The relay IP address used by the client to contact the server\n(i.e. a DHCP relay server).\n', - }, - { - name: 'client_mac', - type: 'keyword', - description: "The client's MAC address (layer two).", - }, - { - name: 'server_name', - type: 'keyword', - description: - 'The name of the server sending the message. Optional. Used in\nDHCPOFFER or DHCPACK messages.\n', - }, - { - name: 'op_code', - type: 'keyword', - example: 'bootreply', - description: 'The message op code (bootrequest or bootreply).\n', - }, - { - name: 'hops', - type: 'long', - description: 'The number of hops the DHCP message went through.', - }, - { - name: 'hardware_type', - type: 'keyword', - description: - 'The type of hardware used for the local network (Ethernet,\nLocalTalk, etc).\n', - }, - { - name: 'option', - type: 'group', - fields: [ - { - name: 'message_type', - type: 'keyword', - example: 'ack', - description: - 'The specific type of DHCP message being sent (e.g. discover,\noffer, request, decline, ack, nak, release, inform).\n', - }, - { - name: 'parameter_request_list', - type: 'keyword', - description: - 'This option is used by a DHCP client to request values for\nspecified configuration parameters.\n', - }, - { - name: 'requested_ip_address', - type: 'ip', - description: - 'This option is used in a client request (DHCPDISCOVER) to allow\nthe client to request that a particular IP address be assigned.\n', - }, - { - name: 'server_identifier', - type: 'ip', - description: - 'IP address of the individual DHCP server which handled this\nmessage.\n', - }, - { - name: 'broadcast_address', - type: 'ip', - description: - "This option specifies the broadcast address in use on the\nclient's subnet.\n", - }, - { - name: 'max_dhcp_message_size', - type: 'long', - description: - 'This option specifies the maximum length DHCP message that the\nclient is willing to accept.\n', - }, - { - name: 'class_identifier', - type: 'keyword', - description: - "This option is used by DHCP clients to optionally identify the\nvendor type and configuration of a DHCP client. Vendors may\nchoose to define specific vendor class identifiers to convey\nparticular configuration or other identification information\nabout a client. For example, the identifier may encode the\nclient's hardware configuration.\n", - }, - { - name: 'domain_name', - type: 'keyword', - description: - 'This option specifies the domain name that client should use\nwhen resolving hostnames via the Domain Name System.\n', - }, - { - name: 'dns_servers', - type: 'ip', - description: - 'The domain name server option specifies a list of Domain Name\nSystem servers available to the client.\n', - }, - { - name: 'vendor_identifying_options', - type: 'object', - description: - 'A DHCP client may use this option to unambiguously identify the\nvendor that manufactured the hardware on which the client is\nrunning, the software in use, or an industry consortium to which\nthe vendor belongs. This field is described in RFC 3925.\n', - }, - { - name: 'subnet_mask', - type: 'ip', - description: - 'The subnet mask that the client should use on the currnet\nnetwork.\n', - }, - { - name: 'utc_time_offset_sec', - type: 'long', - description: - "The time offset field specifies the offset of the client's\nsubnet in seconds from Coordinated Universal Time (UTC).\n", - }, - { - name: 'router', - type: 'ip', - description: - "The router option specifies a list of IP addresses for routers\non the client's subnet.\n", - }, - { - name: 'time_servers', - type: 'ip', - description: - 'The time server option specifies a list of RFC 868 time servers\navailable to the client.\n', - }, - { - name: 'ntp_servers', - type: 'ip', - description: - 'This option specifies a list of IP addresses indicating NTP\nservers available to the client.\n', - }, - { - name: 'hostname', - type: 'keyword', - description: 'This option specifies the name of the client.\n', - }, - { - name: 'ip_address_lease_time_sec', - type: 'long', - description: - 'This option is used in a client request (DHCPDISCOVER or\nDHCPREQUEST) to allow the client to request a lease time for the\nIP address. In a server reply (DHCPOFFER), a DHCP server uses\nthis option to specify the lease time it is willing to offer.\n', - }, - { - name: 'message', - type: 'text', - description: - 'This option is used by a DHCP server to provide an error message\nto a DHCP client in a DHCPNAK message in the event of a failure.\nA client may use this option in a DHCPDECLINE message to\nindicate the why the client declined the offered parameters.\n', - }, - { - name: 'renewal_time_sec', - type: 'long', - description: - 'This option specifies the time interval from address assignment\nuntil the client transitions to the RENEWING state.\n', - }, - { - name: 'rebinding_time_sec', - type: 'long', - description: - 'This option specifies the time interval from address assignment\nuntil the client transitions to the REBINDING state.\n', - }, - { - name: 'boot_file_name', - type: 'keyword', - description: - "This option is used to identify a bootfile when the 'file' field\nin the DHCP header has been used for DHCP options.\n", - }, - ], - }, - ], - }, - ], - }, - { - key: 'dns', - title: 'DNS', - description: 'DNS-specific event fields.', - fields: [ - { - name: 'dns', - type: 'group', - fields: [ - { - name: 'flags.authoritative', - type: 'boolean', - description: - 'A DNS flag specifying that the responding server is an authority for the domain name used in the question.\n', - }, - { - name: 'flags.recursion_available', - type: 'boolean', - description: - 'A DNS flag specifying whether recursive query support is available in the name server.\n', - }, - { - name: 'flags.recursion_desired', - type: 'boolean', - description: - 'A DNS flag specifying that the client directs the server to pursue a query recursively. Recursive query support is optional.\n', - }, - { - name: 'flags.authentic_data', - type: 'boolean', - description: - 'A DNS flag specifying that the recursive server considers the response authentic.\n', - }, - { - name: 'flags.checking_disabled', - type: 'boolean', - description: - 'A DNS flag specifying that the client disables the server signature validation of the query.\n', - }, - { - name: 'flags.truncated_response', - type: 'boolean', - description: - 'A DNS flag specifying that only the first 512 bytes of the reply were returned.\n', - }, - { - name: 'question.etld_plus_one', - description: - 'The effective top-level domain (eTLD) plus one more label.\nFor example, the eTLD+1 for "foo.bar.golang.org." is "golang.org.".\nThe data for determining the eTLD comes from an embedded copy of the\ndata from http://publicsuffix.org.', - example: 'amazon.co.uk.', - }, - { - name: 'answers_count', - type: 'long', - description: 'The number of resource records contained in the `dns.answers` field.\n', - }, - { - name: 'authorities', - type: 'object', - description: - 'An array containing a dictionary for each authority section from the answer.\n', - }, - { - name: 'authorities_count', - type: 'long', - description: - 'The number of resource records contained in the `dns.authorities` field. The `dns.authorities` field may or may not be included depending on the configuration of Packetbeat.\n', - }, - { - name: 'authorities.name', - description: 'The domain name to which this resource record pertains.', - example: 'example.com.', - }, - { - name: 'authorities.type', - description: 'The type of data contained in this resource record.', - example: 'NS', - }, - { - name: 'authorities.class', - description: 'The class of DNS data contained in this resource record.', - example: 'IN', - }, - { - name: 'additionals', - type: 'object', - description: - 'An array containing a dictionary for each additional section from the answer.\n', - }, - { - name: 'additionals_count', - type: 'long', - description: - 'The number of resource records contained in the `dns.additionals` field. The `dns.additionals` field may or may not be included depending on the configuration of Packetbeat.\n', - }, - { - name: 'additionals.name', - description: 'The domain name to which this resource record pertains.', - example: 'example.com.', - }, - { - name: 'additionals.type', - description: 'The type of data contained in this resource record.', - example: 'NS', - }, - { - name: 'additionals.class', - description: 'The class of DNS data contained in this resource record.', - example: 'IN', - }, - { - name: 'additionals.ttl', - description: - 'The time interval in seconds that this resource record may be cached before it should be discarded. Zero values mean that the data should not be cached.\n', - type: 'long', - }, - { - name: 'additionals.data', - description: - 'The data describing the resource. The meaning of this data depends on the type and class of the resource record.\n', - }, - { - name: 'opt.version', - description: 'The EDNS version.', - example: '0', - }, - { - name: 'opt.do', - type: 'boolean', - description: 'If set, the transaction uses DNSSEC.', - }, - { - name: 'opt.ext_rcode', - description: 'Extended response code field.', - example: 'BADVERS', - }, - { - name: 'opt.udp_size', - type: 'long', - description: "Requestor's UDP payload size (in bytes).", - }, - ], - }, - ], - }, - { - key: 'http', - title: 'HTTP', - description: 'HTTP-specific event fields.', - fields: [ - { - name: 'http', - type: 'group', - description: 'Information about the HTTP request and response.', - fields: [ - { - name: 'request', - description: 'HTTP request', - type: 'group', - fields: [ - { - name: 'headers', - type: 'object', - object_type: 'keyword', - description: - 'A map containing the captured header fields from the request. Which headers to capture is configurable. If headers with the same header name are present in the message, they will be separated by commas.\n', - }, - { - name: 'params', - type: 'alias', - migration: true, - path: 'url.query', - }, - ], - }, - { - name: 'response', - description: 'HTTP response', - type: 'group', - fields: [ - { - name: 'status_phrase', - description: 'The HTTP status phrase.', - example: 'Not Found', - }, - { - name: 'headers', - type: 'object', - object_type: 'keyword', - description: - 'A map containing the captured header fields from the response. Which headers to capture is configurable. If headers with the same header name are present in the message, they will be separated by commas.\n', - }, - { - name: 'code', - type: 'alias', - migration: true, - path: 'http.response.status_code', - }, - { - name: 'phrase', - type: 'alias', - migration: true, - path: 'http.response.status_phrase', - }, - ], - }, - ], - }, - ], - }, - { - key: 'icmp', - title: 'ICMP', - description: 'ICMP specific event fields.\n', - fields: [ - { - name: 'icmp', - type: 'group', - fields: [ - { - name: 'version', - description: 'The version of the ICMP protocol.', - possible_values: [4, 6], - }, - { - name: 'request.message', - type: 'keyword', - description: 'A human readable form of the request.', - }, - { - name: 'request.type', - type: 'long', - description: 'The request type.', - }, - { - name: 'request.code', - type: 'long', - description: 'The request code.', - }, - { - name: 'response.message', - type: 'keyword', - description: 'A human readable form of the response.', - }, - { - name: 'response.type', - type: 'long', - description: 'The response type.', - }, - { - name: 'response.code', - type: 'long', - description: 'The response code.', - }, - ], - }, - ], - }, - { - key: 'memcache', - title: 'Memcache', - description: 'Memcached-specific event fields', - fields: [ - { - name: 'memcache', - type: 'group', - fields: [ - { - name: 'protocol_type', - type: 'keyword', - description: - 'The memcache protocol implementation. The value can be "binary" for binary-based, "text" for text-based, or "unknown" for an unknown memcache protocol type.\n', - }, - { - name: 'request.line', - type: 'keyword', - description: 'The raw command line for unknown commands ONLY.\n', - }, - { - name: 'request.command', - type: 'keyword', - description: - 'The memcache command being requested in the memcache text protocol. For example "set" or "get". The binary protocol opcodes are translated into memcache text protocol commands.\n', - }, - { - name: 'response.command', - type: 'keyword', - description: - 'Either the text based protocol response message type or the name of the originating request if binary protocol is used.\n', - }, - { - name: 'request.type', - type: 'keyword', - description: - 'The memcache command classification. This value can be "UNKNOWN", "Load", "Store", "Delete", "Counter", "Info", "SlabCtrl", "LRUCrawler", "Stats", "Success", "Fail", or "Auth".\n', - }, - { - name: 'response.type', - type: 'keyword', - description: - 'The memcache command classification. This value can be "UNKNOWN", "Load", "Store", "Delete", "Counter", "Info", "SlabCtrl", "LRUCrawler", "Stats", "Success", "Fail", or "Auth". The text based protocol will employ any of these, whereas the binary based protocol will mirror the request commands only (see `memcache.response.status` for binary protocol).\n', - }, - { - name: 'response.error_msg', - type: 'keyword', - description: - 'The optional error message in the memcache response (text based protocol only).\n', - }, - { - name: 'request.opcode', - type: 'keyword', - description: 'The binary protocol message opcode name.\n', - }, - { - name: 'response.opcode', - type: 'keyword', - description: 'The binary protocol message opcode name.\n', - }, - { - name: 'request.opcode_value', - type: 'long', - description: 'The binary protocol message opcode value.\n', - }, - { - name: 'response.opcode_value', - type: 'long', - description: 'The binary protocol message opcode value.\n', - }, - { - name: 'request.opaque', - type: 'long', - description: - 'The binary protocol opaque header value used for correlating request with response messages.\n', - }, - { - name: 'response.opaque', - type: 'long', - description: - 'The binary protocol opaque header value used for correlating request with response messages.\n', - }, - { - name: 'request.vbucket', - type: 'long', - description: 'The vbucket index sent in the binary message.\n', - }, - { - name: 'response.status', - type: 'keyword', - description: - 'The textual representation of the response error code (binary protocol only).\n', - }, - { - name: 'response.status_code', - type: 'long', - description: 'The status code value returned in the response (binary protocol only).\n', - }, - { - name: 'request.keys', - type: 'array', - description: 'The list of keys sent in the store or load commands.\n', - }, - { - name: 'response.keys', - type: 'array', - description: 'The list of keys returned for the load command (if present).\n', - }, - { - name: 'request.count_values', - type: 'long', - description: - 'The number of values found in the memcache request message. If the command does not send any data, this field is missing.\n', - }, - { - name: 'response.count_values', - type: 'long', - description: - 'The number of values found in the memcache response message. If the command does not send any data, this field is missing.\n', - }, - { - name: 'request.values', - type: 'array', - description: 'The list of base64 encoded values sent with the request (if present).\n', - }, - { - name: 'response.values', - type: 'array', - description: 'The list of base64 encoded values sent with the response (if present).\n', - }, - { - name: 'request.bytes', - type: 'long', - format: 'bytes', - description: 'The byte count of the values being transferred.\n', - }, - { - name: 'response.bytes', - type: 'long', - format: 'bytes', - description: 'The byte count of the values being transferred.\n', - }, - { - name: 'request.delta', - type: 'long', - description: 'The counter increment/decrement delta value.\n', - }, - { - name: 'request.initial', - type: 'long', - description: - 'The counter increment/decrement initial value parameter (binary protocol only).\n', - }, - { - name: 'request.verbosity', - type: 'long', - description: 'The value of the memcache "verbosity" command.\n', - }, - { - name: 'request.raw_args', - type: 'keyword', - description: - 'The text protocol raw arguments for the "stats ..." and "lru crawl ..." commands.\n', - }, - { - name: 'request.source_class', - type: 'long', - description: "The source class id in 'slab reassign' command.\n", - }, - { - name: 'request.dest_class', - type: 'long', - description: "The destination class id in 'slab reassign' command.\n", - }, - { - name: 'request.automove', - type: 'keyword', - description: - 'The automove mode in the \'slab automove\' command expressed as a string. This value can be "standby"(=0), "slow"(=1), "aggressive"(=2), or the raw value if the value is unknown.\n', - }, - { - name: 'request.flags', - type: 'long', - description: 'The memcache command flags sent in the request (if present).\n', - }, - { - name: 'response.flags', - type: 'long', - description: 'The memcache message flags sent in the response (if present).\n', - }, - { - name: 'request.exptime', - type: 'long', - description: - 'The data expiry time in seconds sent with the memcache command (if present). If the value is <30 days, the expiry time is relative to "now", or else it is an absolute Unix time in seconds (32-bit).\n', - }, - { - name: 'request.sleep_us', - type: 'long', - description: "The sleep setting in microseconds for the 'lru_crawler sleep' command.\n", - }, - { - name: 'response.value', - type: 'long', - description: 'The counter value returned by a counter operation.\n', - }, - { - name: 'request.noreply', - type: 'boolean', - description: - 'Set to true if noreply was set in the request. The `memcache.response` field will be missing.\n', - }, - { - name: 'request.quiet', - type: 'boolean', - description: - 'Set to true if the binary protocol message is to be treated as a quiet message.\n', - }, - { - name: 'request.cas_unique', - type: 'long', - description: 'The CAS (compare-and-swap) identifier if present.\n', - }, - { - name: 'response.cas_unique', - type: 'long', - description: - 'The CAS (compare-and-swap) identifier to be used with CAS-based updates (if present).\n', - }, - { - name: 'response.stats', - type: 'array', - description: - 'The list of statistic values returned. Each entry is a dictionary with the fields "name" and "value".\n', - }, - { - name: 'response.version', - type: 'keyword', - description: 'The returned memcache version string.\n', - }, - ], - }, - ], - }, - { - key: 'mongodb', - title: 'MongoDb', - description: - 'MongoDB-specific event fields. These fields mirror closely the fields for the MongoDB wire protocol. The higher level fields (for example, `query` and `resource`) apply to MongoDB events as well.\n', - fields: [ - { - name: 'mongodb', - type: 'group', - fields: [ - { - name: 'error', - description: - 'If the MongoDB request has resulted in an error, this field contains the error message returned by the server.\n', - }, - { - name: 'fullCollectionName', - description: - 'The full collection name. The full collection name is the concatenation of the database name with the collection name, using a dot (.) for the concatenation. For example, for the database foo and the collection bar, the full collection name is foo.bar.\n', - }, - { - name: 'numberToSkip', - type: 'long', - description: - 'Sets the number of documents to omit - starting from the first document in the resulting dataset - when returning the result of the query.\n', - }, - { - name: 'numberToReturn', - type: 'long', - description: 'The requested maximum number of documents to be returned.\n', - }, - { - name: 'numberReturned', - type: 'long', - description: 'The number of documents in the reply.\n', - }, - { - name: 'startingFrom', - description: 'Where in the cursor this reply is starting.\n', - }, - { - name: 'query', - description: - 'A JSON document that represents the query. The query will contain one or more elements, all of which must match for a document to be included in the result set. Possible elements include $query, $orderby, $hint, $explain, and $snapshot.\n', - }, - { - name: 'returnFieldsSelector', - description: - 'A JSON document that limits the fields in the returned documents. The returnFieldsSelector contains one or more elements, each of which is the name of a field that should be returned, and the integer value 1.\n', - }, - { - name: 'selector', - description: - 'A BSON document that specifies the query for selecting the document to update or delete.\n', - }, - { - name: 'update', - description: - 'A BSON document that specifies the update to be performed. For information on specifying updates, see the Update Operations documentation from the MongoDB Manual.\n', - }, - { - name: 'cursorId', - description: - 'The cursor identifier returned in the OP_REPLY. This must be the value that was returned from the database.\n', - }, - ], - }, - ], - }, - { - key: 'mysql', - title: 'MySQL', - description: 'MySQL-specific event fields.\n', - fields: [ - { - name: 'mysql', - type: 'group', - fields: [ - { - name: 'affected_rows', - type: 'long', - description: - 'If the MySQL command is successful, this field contains the affected number of rows of the last statement.\n', - }, - { - name: 'insert_id', - description: - 'If the INSERT query is successful, this field contains the id of the newly inserted row.\n', - }, - { - name: 'num_fields', - description: - 'If the SELECT query is successful, this field is set to the number of fields returned.\n', - }, - { - name: 'num_rows', - description: - 'If the SELECT query is successful, this field is set to the number of rows returned.\n', - }, - { - name: 'query', - description: "The row mysql query as read from the transaction's request.\n", - }, - { - name: 'error_code', - type: 'long', - description: 'The error code returned by MySQL.\n', - }, - { - name: 'error_message', - description: 'The error info message returned by MySQL.\n', - }, - ], - }, - ], - }, - { - key: 'nfs', - title: 'NFS', - description: 'NFS v4/3 specific event fields.', - fields: [ - { - name: 'nfs', - type: 'group', - fields: [ - { - name: 'version', - type: 'long', - description: 'NFS protocol version number.', - }, - { - name: 'minor_version', - type: 'long', - description: 'NFS protocol minor version number.', - }, - { - name: 'tag', - description: 'NFS v4 COMPOUND operation tag.', - }, - { - name: 'opcode', - description: 'NFS operation name, or main operation name, in case of COMPOUND calls.\n', - }, - { - name: 'status', - description: 'NFS operation reply status.', - }, - ], - }, - { - name: 'rpc', - type: 'group', - description: 'ONC RPC specific event fields.', - fields: [ - { - name: 'xid', - description: 'RPC message transaction identifier.', - }, - { - name: 'status', - description: 'RPC message reply status.', - }, - { - name: 'auth_flavor', - description: 'RPC authentication flavor.', - }, - { - name: 'cred.uid', - type: 'long', - description: "RPC caller's user id, in case of auth-unix.", - }, - { - name: 'cred.gid', - type: 'long', - description: "RPC caller's group id, in case of auth-unix.", - }, - { - name: 'cred.gids', - description: "RPC caller's secondary group ids, in case of auth-unix.", - }, - { - name: 'cred.stamp', - type: 'long', - description: 'Arbitrary ID which the caller machine may generate.', - }, - { - name: 'cred.machinename', - description: "The name of the caller's machine.", - }, - { - name: 'call_size', - type: 'alias', - path: 'source.bytes', - migration: true, - description: 'RPC call size with argument.', - }, - { - name: 'reply_size', - type: 'alias', - path: 'destination.bytes', - migration: true, - description: 'RPC reply size with argument.', - }, - ], - }, - ], - }, - { - key: 'pgsql', - title: 'PostgreSQL', - description: 'PostgreSQL-specific event fields.\n', - fields: [ - { - name: 'pgsql', - type: 'group', - fields: [ - { - name: 'error_code', - description: 'The PostgreSQL error code.', - type: 'long', - }, - { - name: 'error_message', - description: 'The PostgreSQL error message.', - }, - { - name: 'error_severity', - description: 'The PostgreSQL error severity.', - possible_values: ['ERROR', 'FATAL', 'PANIC'], - }, - { - name: 'num_fields', - description: - 'If the SELECT query if successful, this field is set to the number of fields returned.\n', - }, - { - name: 'num_rows', - description: - 'If the SELECT query if successful, this field is set to the number of rows returned.\n', - }, - ], - }, - ], - }, - { - key: 'redis', - title: 'Redis', - description: 'Redis-specific event fields.\n', - fields: [ - { - name: 'redis', - type: 'group', - fields: [ - { - name: 'return_value', - description: 'The return value of the Redis command in a human readable format.\n', - }, - { - name: 'error', - description: - 'If the Redis command has resulted in an error, this field contains the error message returned by the Redis server.\n', - }, - ], - }, - ], - }, - { - key: 'thrift', - title: 'Thrift-RPC', - description: 'Thrift-RPC specific event fields.\n', - fields: [ - { - name: 'thrift', - type: 'group', - fields: [ - { - name: 'params', - description: - 'The RPC method call parameters in a human readable format. If the IDL files are available, the parameters use names whenever possible. Otherwise, the IDs from the message are used.\n', - }, - { - name: 'service', - description: 'The name of the Thrift-RPC service as defined in the IDL files.\n', - }, - { - name: 'return_value', - description: - 'The value returned by the Thrift-RPC call. This is encoded in a human readable format.\n', - }, - { - name: 'exceptions', - description: - 'If the call resulted in exceptions, this field contains the exceptions in a human readable format.\n', - }, - ], - }, - ], - }, - { - key: 'tls_detailed', - title: 'Detailed TLS', - description: 'Detailed TLS-specific event fields.\n', - fields: [ - { - name: 'tls', - type: 'group', - fields: [ - { - name: 'detailed', - type: 'group', - default_fields: false, - fields: [ - { - name: 'version', - type: 'keyword', - description: 'The version of the TLS protocol used.\n', - example: 'TLS 1.3', - }, - { - name: 'resumption_method', - type: 'keyword', - description: - 'If the session has been resumed, the underlying method used. One of "id" for TLS session ID or "ticket" for TLS ticket extension.\n', - }, - { - name: 'client_certificate_requested', - type: 'boolean', - description: - 'Whether the server has requested the client to authenticate itself using a client certificate.\n', - }, - { - name: 'client_hello', - type: 'group', - fields: [ - { - name: 'version', - type: 'keyword', - description: - 'The version of the TLS protocol by which the client wishes to communicate during this session.\n', - }, - { - name: 'session_id', - type: 'keyword', - description: - 'Unique number to identify the session for the corresponding connection with the client.\n', - }, - { - name: 'supported_compression_methods', - type: 'keyword', - description: - 'The list of compression methods the client supports. See https://www.iana.org/assignments/comp-meth-ids/comp-meth-ids.xhtml\n', - }, - { - name: 'extensions', - type: 'group', - description: 'The hello extensions provided by the client.', - fields: [ - { - name: 'server_name_indication', - type: 'keyword', - description: 'List of hostnames', - }, - { - name: 'application_layer_protocol_negotiation', - type: 'keyword', - description: - 'List of application-layer protocols the client is willing to use.\n', - }, - { - name: 'session_ticket', - type: 'keyword', - description: - 'Length of the session ticket, if provided, or an empty string to advertise support for tickets.\n', - }, - { - name: 'supported_versions', - type: 'keyword', - description: 'List of TLS versions that the client is willing to use.\n', - }, - { - name: 'supported_groups', - type: 'keyword', - description: - 'List of Elliptic Curve Cryptography (ECC) curve groups supported by the client.\n', - }, - { - name: 'signature_algorithms', - type: 'keyword', - description: - 'List of signature algorithms that may be use in digital signatures.\n', - }, - { - name: 'ec_points_formats', - type: 'keyword', - description: - 'List of Elliptic Curve (EC) point formats. Indicates the set of point formats that the client can parse.\n', - }, - { - name: '_unparsed_', - type: 'keyword', - description: 'List of extensions that were left unparsed by Packetbeat.\n', - }, - ], - }, - ], - }, - { - name: 'server_hello', - type: 'group', - fields: [ - { - name: 'version', - type: 'keyword', - description: - 'The version of the TLS protocol that is used for this session. It is the highest version supported by the server not exceeding the version requested in the client hello.\n', - }, - { - name: 'selected_compression_method', - type: 'keyword', - description: - 'The compression method selected by the server from the list provided in the client hello.\n', - }, - { - name: 'session_id', - type: 'keyword', - description: - 'Unique number to identify the session for the corresponding connection with the client.\n', - }, - { - name: 'extensions', - type: 'group', - description: 'The hello extensions provided by the server.', - fields: [ - { - name: 'application_layer_protocol_negotiation', - type: 'keyword', - description: 'Negotiated application layer protocol', - }, - { - name: 'session_ticket', - type: 'keyword', - description: - 'Used to announce that a session ticket will be provided by the server. Always an empty string.\n', - }, - { - name: 'supported_versions', - type: 'keyword', - description: 'Negotiated TLS version to be used.\n', - }, - { - name: 'ec_points_formats', - type: 'keyword', - description: - 'List of Elliptic Curve (EC) point formats. Indicates the set of point formats that the server can parse.\n', - }, - { - name: '_unparsed_', - type: 'keyword', - description: 'List of extensions that were left unparsed by Packetbeat.\n', - }, - ], - }, - ], - }, - { - name: 'client_certificate', - type: 'group', - description: 'Certificate provided by the client for authentication.', - fields: [ - { - name: 'version', - type: 'long', - description: 'X509 format version.', - }, - { - name: 'serial_number', - type: 'keyword', - description: "The certificate's serial number.", - }, - { - name: 'not_before', - type: 'date', - description: 'Date before which the certificate is not valid.', - }, - { - name: 'not_after', - type: 'date', - description: 'Date after which the certificate expires.', - }, - { - name: 'public_key_algorithm', - type: 'keyword', - description: - "The algorithm used for this certificate's public key. One of RSA, DSA or ECDSA.\n", - }, - { - name: 'public_key_size', - type: 'long', - description: 'Size of the public key.', - }, - { - name: 'signature_algorithm', - type: 'keyword', - description: "The algorithm used for the certificate's signature.\n", - }, - { - name: 'alternative_names', - type: 'keyword', - description: 'Subject Alternative Names for this certificate.', - }, - { - name: 'subject', - type: 'group', - description: 'Subject represented by this certificate.', - fields: [ - { - name: 'country', - type: 'keyword', - description: 'Country code.', - }, - { - name: 'organization', - type: 'keyword', - description: 'Organization name.', - }, - { - name: 'organizational_unit', - type: 'keyword', - description: 'Unit within organization.', - }, - { - name: 'province', - type: 'keyword', - description: 'Province or region within country.', - }, - { - name: 'common_name', - type: 'keyword', - description: 'Name or host name identified by the certificate.', - }, - { - name: 'locality', - type: 'keyword', - description: 'Locality.', - }, - ], - }, - { - name: 'issuer', - type: 'group', - description: 'Entity that issued and signed this certificate.', - fields: [ - { - name: 'country', - type: 'keyword', - description: 'Country code.', - }, - { - name: 'organization', - type: 'keyword', - description: 'Organization name.', - }, - { - name: 'organizational_unit', - type: 'keyword', - description: 'Unit within organization.', - }, - { - name: 'province', - type: 'keyword', - description: 'Province or region within country.', - }, - { - name: 'common_name', - type: 'keyword', - description: 'Name or host name identified by the certificate.', - }, - { - name: 'locality', - type: 'keyword', - description: 'Locality.', - }, - ], - }, - ], - }, - { - name: 'server_certificate', - type: 'group', - description: 'Certificate provided by the server for authentication.', - fields: [ - { - name: 'version', - type: 'long', - description: 'X509 format version.', - }, - { - name: 'serial_number', - type: 'keyword', - description: "The certificate's serial number.", - }, - { - name: 'not_before', - type: 'date', - description: 'Date before which the certificate is not valid.', - }, - { - name: 'not_after', - type: 'date', - description: 'Date after which the certificate expires.', - }, - { - name: 'public_key_algorithm', - type: 'keyword', - description: - "The algorithm used for this certificate's public key. One of RSA, DSA or ECDSA.\n", - }, - { - name: 'public_key_size', - type: 'long', - description: 'Size of the public key.', - }, - { - name: 'signature_algorithm', - type: 'keyword', - description: "The algorithm used for the certificate's signature.\n", - }, - { - name: 'alternative_names', - type: 'keyword', - description: 'Subject Alternative Names for this certificate.', - }, - { - name: 'subject', - type: 'group', - description: 'Subject represented by this certificate.', - fields: [ - { - name: 'country', - type: 'keyword', - description: 'Country code.', - }, - { - name: 'organization', - type: 'keyword', - description: 'Organization name.', - }, - { - name: 'organizational_unit', - type: 'keyword', - description: 'Unit within organization.', - }, - { - name: 'province', - type: 'keyword', - description: 'Province or region within country.', - }, - { - name: 'common_name', - type: 'keyword', - description: 'Name or host name identified by the certificate.', - }, - { - name: 'locality', - type: 'keyword', - description: 'Locality.', - }, - ], - }, - { - name: 'issuer', - type: 'group', - description: 'Entity that issued and signed this certificate.', - fields: [ - { - name: 'country', - type: 'keyword', - description: 'Country code.', - }, - { - name: 'organization', - type: 'keyword', - description: 'Organization name.', - }, - { - name: 'organizational_unit', - type: 'keyword', - description: 'Unit within organization.', - }, - { - name: 'province', - type: 'keyword', - description: 'Province or region within country.', - }, - { - name: 'common_name', - type: 'keyword', - description: 'Name or host name identified by the certificate.', - }, - { - name: 'locality', - type: 'keyword', - description: 'Locality.', - }, - ], - }, - ], - }, - { - name: 'server_certificate_chain', - type: 'array', - description: 'Chain of trust for the server certificate.', - }, - { - name: 'client_certificate_chain', - type: 'array', - description: 'Chain of trust for the client certificate.', - }, - { - name: 'alert_types', - type: 'keyword', - description: 'An array containing the TLS alert type for every alert received.\n', - }, - ], - }, - ], - }, - { - name: 'tls.handshake_completed', - type: 'alias', - path: 'tls.established', - }, - { - name: 'tls.client_hello.supported_ciphers', - type: 'alias', - path: 'tls.client.supported_ciphers', - }, - { - name: 'tls.server_hello.selected_cipher', - type: 'alias', - path: 'tls.cipher', - }, - { - name: 'tls.fingerprints.ja3', - type: 'alias', - path: 'tls.client.ja3', - }, - { - name: 'tls.resumption_method', - type: 'alias', - path: 'tls.detailed.resumption_method', - }, - { - name: 'tls.client_certificate_requested', - type: 'alias', - path: 'tls.detailed.client_certificate_requested', - }, - { - name: 'tls.client_hello.version', - type: 'alias', - path: 'tls.detailed.client_hello.version', - }, - { - name: 'tls.client_hello.session_id', - type: 'alias', - path: 'tls.detailed.client_hello.session_id', - }, - { - name: 'tls.client_hello.supported_compression_methods', - type: 'alias', - path: 'tls.detailed.client_hello.supported_compression_methods', - }, - { - name: 'tls.client_hello.extensions.server_name_indication', - type: 'alias', - path: 'tls.detailed.client_hello.extensions.server_name_indication', - }, - { - name: 'tls.client_hello.extensions.application_layer_protocol_negotiation', - type: 'alias', - path: 'tls.detailed.client_hello.extensions.application_layer_protocol_negotiation', - }, - { - name: 'tls.client_hello.extensions.session_ticket', - type: 'alias', - path: 'tls.detailed.client_hello.extensions.session_ticket', - }, - { - name: 'tls.client_hello.extensions.supported_versions', - type: 'alias', - path: 'tls.detailed.client_hello.extensions.supported_versions', - }, - { - name: 'tls.client_hello.extensions.supported_groups', - type: 'alias', - path: 'tls.detailed.client_hello.extensions.supported_groups', - }, - { - name: 'tls.client_hello.extensions.signature_algorithms', - type: 'alias', - path: 'tls.detailed.client_hello.extensions.signature_algorithms', - }, - { - name: 'tls.client_hello.extensions.ec_points_formats', - type: 'alias', - path: 'tls.detailed.client_hello.extensions.ec_points_formats', - }, - { - name: 'tls.client_hello.extensions._unparsed_', - type: 'alias', - path: 'tls.detailed.client_hello.extensions._unparsed_', - }, - { - name: 'tls.server_hello.version', - type: 'alias', - path: 'tls.detailed.server_hello.version', - }, - { - name: 'tls.server_hello.selected_compression_method', - type: 'alias', - path: 'tls.detailed.server_hello.selected_compression_method', - }, - { - name: 'tls.server_hello.session_id', - type: 'alias', - path: 'tls.detailed.server_hello.session_id', - }, - { - name: 'tls.server_hello.extensions.application_layer_protocol_negotiation', - type: 'alias', - path: 'tls.detailed.server_hello.extensions.application_layer_protocol_negotiation', - }, - { - name: 'tls.server_hello.extensions.session_ticket', - type: 'alias', - path: 'tls.detailed.server_hello.extensions.session_ticket', - }, - { - name: 'tls.server_hello.extensions.supported_versions', - type: 'alias', - path: 'tls.detailed.server_hello.extensions.supported_versions', - }, - { - name: 'tls.server_hello.extensions.ec_points_formats', - type: 'alias', - path: 'tls.detailed.server_hello.extensions.ec_points_formats', - }, - { - name: 'tls.server_hello.extensions._unparsed_', - type: 'alias', - path: 'tls.detailed.server_hello.extensions._unparsed_', - }, - { - name: 'tls.client_certificate.version', - type: 'alias', - path: 'tls.detailed.client_certificate.version', - }, - { - name: 'tls.client_certificate.serial_number', - type: 'alias', - path: 'tls.detailed.client_certificate.serial_number', - }, - { - name: 'tls.client_certificate.not_before', - type: 'alias', - path: 'tls.detailed.client_certificate.not_before', - }, - { - name: 'tls.client_certificate.not_after', - type: 'alias', - path: 'tls.detailed.client_certificate.not_after', - }, - { - name: 'tls.client_certificate.public_key_algorithm', - type: 'alias', - path: 'tls.detailed.client_certificate.public_key_algorithm', - }, - { - name: 'tls.client_certificate.public_key_size', - type: 'alias', - path: 'tls.detailed.client_certificate.public_key_size', - }, - { - name: 'tls.client_certificate.signature_algorithm', - type: 'alias', - path: 'tls.detailed.client_certificate.signature_algorithm', - }, - { - name: 'tls.client_certificate.alternative_names', - type: 'alias', - path: 'tls.detailed.client_certificate.alternative_names', - }, - { - name: 'tls.client_certificate.subject.country', - type: 'alias', - path: 'tls.detailed.client_certificate.subject.country', - }, - { - name: 'tls.client_certificate.subject.organization', - type: 'alias', - path: 'tls.detailed.client_certificate.subject.organization', - }, - { - name: 'tls.client_certificate.subject.organizational_unit', - type: 'alias', - path: 'tls.detailed.client_certificate.subject.organizational_unit', - }, - { - name: 'tls.client_certificate.subject.province', - type: 'alias', - path: 'tls.detailed.client_certificate.subject.province', - }, - { - name: 'tls.client_certificate.subject.common_name', - type: 'alias', - path: 'tls.detailed.client_certificate.subject.common_name', - }, - { - name: 'tls.client_certificate.subject.locality', - type: 'alias', - path: 'tls.detailed.client_certificate.subject.locality', - }, - { - name: 'tls.client_certificate.issuer.country', - type: 'alias', - path: 'tls.detailed.client_certificate.issuer.country', - }, - { - name: 'tls.client_certificate.issuer.organization', - type: 'alias', - path: 'tls.detailed.client_certificate.issuer.organization', - }, - { - name: 'tls.client_certificate.issuer.organizational_unit', - type: 'alias', - path: 'tls.detailed.client_certificate.issuer.organizational_unit', - }, - { - name: 'tls.client_certificate.issuer.province', - type: 'alias', - path: 'tls.detailed.client_certificate.issuer.province', - }, - { - name: 'tls.client_certificate.issuer.common_name', - type: 'alias', - path: 'tls.detailed.client_certificate.issuer.common_name', - }, - { - name: 'tls.client_certificate.issuer.locality', - type: 'alias', - path: 'tls.detailed.client_certificate.issuer.locality', - }, - { - name: 'tls.server_certificate.version', - type: 'alias', - path: 'tls.detailed.server_certificate.version', - }, - { - name: 'tls.server_certificate.serial_number', - type: 'alias', - path: 'tls.detailed.server_certificate.serial_number', - }, - { - name: 'tls.server_certificate.not_before', - type: 'alias', - path: 'tls.detailed.server_certificate.not_before', - }, - { - name: 'tls.server_certificate.not_after', - type: 'alias', - path: 'tls.detailed.server_certificate.not_after', - }, - { - name: 'tls.server_certificate.public_key_algorithm', - type: 'alias', - path: 'tls.detailed.server_certificate.public_key_algorithm', - }, - { - name: 'tls.server_certificate.public_key_size', - type: 'alias', - path: 'tls.detailed.server_certificate.public_key_size', - }, - { - name: 'tls.server_certificate.signature_algorithm', - type: 'alias', - path: 'tls.detailed.server_certificate.signature_algorithm', - }, - { - name: 'tls.server_certificate.alternative_names', - type: 'alias', - path: 'tls.detailed.server_certificate.alternative_names', - }, - { - name: 'tls.server_certificate.subject.country', - type: 'alias', - path: 'tls.detailed.server_certificate.subject.country', - }, - { - name: 'tls.server_certificate.subject.organization', - type: 'alias', - path: 'tls.detailed.server_certificate.subject.organization', - }, - { - name: 'tls.server_certificate.subject.organizational_unit', - type: 'alias', - path: 'tls.detailed.server_certificate.subject.organizational_unit', - }, - { - name: 'tls.server_certificate.subject.province', - type: 'alias', - path: 'tls.detailed.server_certificate.subject.province', - }, - { - name: 'tls.server_certificate.subject.common_name', - type: 'alias', - path: 'tls.detailed.server_certificate.subject.common_name', - }, - { - name: 'tls.server_certificate.subject.locality', - type: 'alias', - path: 'tls.detailed.server_certificate.subject.locality', - }, - { - name: 'tls.server_certificate.issuer.country', - type: 'alias', - path: 'tls.detailed.server_certificate.issuer.country', - }, - { - name: 'tls.server_certificate.issuer.organization', - type: 'alias', - path: 'tls.detailed.server_certificate.issuer.organization', - }, - { - name: 'tls.server_certificate.issuer.organizational_unit', - type: 'alias', - path: 'tls.detailed.server_certificate.issuer.organizational_unit', - }, - { - name: 'tls.server_certificate.issuer.province', - type: 'alias', - path: 'tls.detailed.server_certificate.issuer.province', - }, - { - name: 'tls.server_certificate.issuer.common_name', - type: 'alias', - path: 'tls.detailed.server_certificate.issuer.common_name', - }, - { - name: 'tls.server_certificate.issuer.locality', - type: 'alias', - path: 'tls.detailed.server_certificate.issuer.locality', - }, - { - name: 'tls.alert_types', - type: 'alias', - path: 'tls.detailed.alert_types', - }, - ], - }, -]; diff --git a/x-pack/plugins/security_solution/server/utils/beat_schema/8.0.0/winlogbeat.ts b/x-pack/plugins/security_solution/server/utils/beat_schema/8.0.0/winlogbeat.ts deleted file mode 100644 index 7457cb3f4428f..0000000000000 --- a/x-pack/plugins/security_solution/server/utils/beat_schema/8.0.0/winlogbeat.ts +++ /dev/null @@ -1,2844 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -/** - * An instance of the unmodified schema exported from winlogbeat-8.0.0-SNAPSHOT-windows-x86_64.zip - * - */ - -import { Schema } from '../type'; - -export const winlogbeatSchema: Schema = [ - { - key: 'ecs', - title: 'ECS', - description: 'ECS Fields.', - fields: [ - { - name: '@timestamp', - level: 'core', - required: true, - type: 'date', - description: - 'Date/time when the event originated.\n\nThis is the date/time extracted from the event, typically representing when\nthe event was generated by the source.\n\nIf the event source has no original timestamp, this value is typically populated\nby the first time the event was received by the pipeline.\n\nRequired field for all events.', - example: '2016-05-23T08:05:34.853Z', - }, - { - name: 'labels', - level: 'core', - type: 'object', - object_type: 'keyword', - description: - 'Custom key/value pairs.\n\nCan be used to add meta information to events. Should not contain nested objects.\nAll values are stored as keyword.\n\nExample: `docker` and `k8s` labels.', - example: { - application: 'foo-bar', - env: 'production', - }, - }, - { - name: 'message', - level: 'core', - type: 'text', - description: - 'For log events the message field contains the log message, optimized\nfor viewing in a log viewer.\n\nFor structured logs without an original message field, other fields can be concatenated\nto form a human-readable summary of the event.\n\nIf multiple messages exist, they can be combined into one message.', - example: 'Hello World', - }, - { - name: 'tags', - level: 'core', - type: 'keyword', - description: 'List of keywords used to tag each event.', - example: '["production", "env2"]', - }, - { - name: 'agent', - title: 'Agent', - group: 2, - description: - 'The agent fields contain the data about the software entity, if\nany, that collects, detects, or observes events on a host, or takes measurements\non a host.\n\nExamples include Beats. Agents may also run on observers. ECS agent.* fields\nshall be populated with details of the agent running on the host or observer\nwhere the event happened or the measurement was taken.', - footnote: - 'Examples: In the case of Beats for logs, the agent.name is filebeat.\nFor APM, it is the agent running in the app/service. The agent information does\nnot change if data is sent through queuing systems like Kafka, Redis, or processing\nsystems such as Logstash or APM Server.', - type: 'group', - fields: [ - { - name: 'ephemeral_id', - level: 'extended', - type: 'keyword', - description: - 'Ephemeral identifier of this agent (if one exists).\n\nThis id normally changes across restarts, but `agent.id` does not.', - example: '8a4f500f', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - description: - 'Unique identifier of this agent (if one exists).\n\nExample: For Beats this would be beat.id.', - example: '8a4f500d', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - description: - 'Custom name of the agent.\n\nThis is a name that can be given to an agent. This can be helpful if for example\ntwo Filebeat instances are running on the same host but a human readable separation\nis needed on which Filebeat instance data is coming from.\n\nIf no name is given, the name is often left empty.', - example: 'foo', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - description: - 'Type of the agent.\n\nThe agent type stays always the same and should be given by the agent used.\nIn case of Filebeat the agent would always be Filebeat also if two Filebeat\ninstances are run on the same machine.', - example: 'filebeat', - }, - { - name: 'version', - level: 'core', - type: 'keyword', - description: 'Version of the agent.', - example: '6.0.0-rc2', - }, - ], - }, - { - name: 'client', - title: 'Client', - group: 2, - description: - 'A client is defined as the initiator of a network connection for\nevents regarding sessions, connections, or bidirectional flow records.\n\nFor TCP events, the client is the initiator of the TCP connection that sends\nthe SYN packet(s). For other protocols, the client is generally the initiator\nor requestor in the network transaction. Some systems use the term "originator"\nto refer the client in TCP connections. The client fields describe details about\nthe system acting as the client in the network event. Client fields are usually\npopulated in conjunction with server fields. Client fields are generally not\npopulated for packet-level events.\n\nClient / server representations can add semantic context to an exchange, which\nis helpful to visualize the data in certain situations. If your context falls\nin that category, you should still ensure that source and destination are filled\nappropriately.', - type: 'group', - fields: [ - { - name: 'address', - level: 'extended', - type: 'keyword', - description: - 'Some event client addresses are defined ambiguously. The event\nwill sometimes list an IP, a domain or a unix socket. You should always store\nthe raw address in the `.address` field.\n\nThen it should be duplicated to `.ip` or `.domain`, depending on which one\nit is.', - }, - { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - description: 'Bytes sent from the client to the server.', - example: 184, - }, - { - name: 'domain', - level: 'core', - type: 'keyword', - description: 'Client domain.', - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: - 'IP address of the client.\n\nCan be one or multiple IPv4 or IPv6 addresses.', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - description: 'MAC address of the client.', - }, - { - name: 'packets', - level: 'core', - type: 'long', - description: 'Packets sent from the client to the server.', - example: 12, - }, - { - name: 'port', - level: 'core', - type: 'long', - format: 'string', - description: 'Port of the client.', - }, - { - name: 'user.email', - level: 'extended', - type: 'keyword', - description: 'User email address.', - }, - { - name: 'user.full_name', - level: 'extended', - type: 'keyword', - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'user.group.id', - level: 'extended', - type: 'keyword', - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'user.group.name', - level: 'extended', - type: 'keyword', - description: 'Name of the group.', - }, - { - name: 'user.hash', - level: 'extended', - type: 'keyword', - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'user.id', - level: 'core', - type: 'keyword', - description: 'One or multiple unique identifiers of the user.', - }, - { - name: 'user.name', - level: 'core', - type: 'keyword', - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'cloud', - title: 'Cloud', - group: 2, - description: 'Fields related to the cloud or infrastructure the events are coming\nfrom.', - footnote: - 'Examples: If Metricbeat is running on an EC2 host and fetches data\nfrom its host, the cloud info contains the data about this machine. If Metricbeat\nruns on a remote machine outside the cloud and fetches data from a service running\nin the cloud, the field contains cloud data from the machine the service is\nrunning on.', - type: 'group', - fields: [ - { - name: 'account.id', - level: 'extended', - type: 'keyword', - description: - 'The cloud account or organization id used to identify different\nentities in a multi-tenant environment.\n\nExamples: AWS account id, Google Cloud ORG Id, or other unique identifier.', - example: 666777888999, - }, - { - name: 'availability_zone', - level: 'extended', - type: 'keyword', - description: 'Availability zone in which this host is running.', - example: 'us-east-1c', - }, - { - name: 'instance.id', - level: 'extended', - type: 'keyword', - description: 'Instance ID of the host machine.', - example: 'i-1234567890abcdef0', - }, - { - name: 'instance.name', - level: 'extended', - type: 'keyword', - description: 'Instance name of the host machine.', - }, - { - name: 'machine.type', - level: 'extended', - type: 'keyword', - description: 'Machine type of the host machine.', - example: 't2.medium', - }, - { - name: 'provider', - level: 'extended', - type: 'keyword', - description: - 'Name of the cloud provider. Example values are aws, azure, gcp,\nor digitalocean.', - example: 'aws', - }, - { - name: 'region', - level: 'extended', - type: 'keyword', - description: 'Region in which this host is running.', - example: 'us-east-1', - }, - ], - }, - { - name: 'container', - title: 'Container', - group: 2, - description: - 'Container fields are used for meta information about the specific\ncontainer that is the source of information.\n\nThese fields help correlate data based containers from any runtime.', - type: 'group', - fields: [ - { - name: 'id', - level: 'core', - type: 'keyword', - description: 'Unique container id.', - }, - { - name: 'image.name', - level: 'extended', - type: 'keyword', - description: 'Name of the image the container was built on.', - }, - { - name: 'image.tag', - level: 'extended', - type: 'keyword', - description: 'Container image tag.', - }, - { - name: 'labels', - level: 'extended', - type: 'object', - object_type: 'keyword', - description: 'Image labels.', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - description: 'Container name.', - }, - { - name: 'runtime', - level: 'extended', - type: 'keyword', - description: 'Runtime managing this container.', - example: 'docker', - }, - ], - }, - { - name: 'destination', - title: 'Destination', - group: 2, - description: - 'Destination fields describe details about the destination of a packet/event.\n\nDestination fields are usually populated in conjunction with source fields.', - type: 'group', - fields: [ - { - name: 'address', - level: 'extended', - type: 'keyword', - description: - 'Some event destination addresses are defined ambiguously. The\nevent will sometimes list an IP, a domain or a unix socket. You should always\nstore the raw address in the `.address` field.\n\nThen it should be duplicated to `.ip` or `.domain`, depending on which one\nit is.', - }, - { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - description: 'Bytes sent from the destination to the source.', - example: 184, - }, - { - name: 'domain', - level: 'core', - type: 'keyword', - description: 'Destination domain.', - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: - 'IP address of the destination.\n\nCan be one or multiple IPv4 or IPv6 addresses.', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - description: 'MAC address of the destination.', - }, - { - name: 'packets', - level: 'core', - type: 'long', - description: 'Packets sent from the destination to the source.', - example: 12, - }, - { - name: 'port', - level: 'core', - type: 'long', - format: 'string', - description: 'Port of the destination.', - }, - { - name: 'user.email', - level: 'extended', - type: 'keyword', - description: 'User email address.', - }, - { - name: 'user.full_name', - level: 'extended', - type: 'keyword', - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'user.group.id', - level: 'extended', - type: 'keyword', - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'user.group.name', - level: 'extended', - type: 'keyword', - description: 'Name of the group.', - }, - { - name: 'user.hash', - level: 'extended', - type: 'keyword', - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'user.id', - level: 'core', - type: 'keyword', - description: 'One or multiple unique identifiers of the user.', - }, - { - name: 'user.name', - level: 'core', - type: 'keyword', - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'ecs', - title: 'ECS', - group: 2, - description: 'Meta-information specific to ECS.', - type: 'group', - fields: [ - { - name: 'version', - level: 'core', - required: true, - type: 'keyword', - description: - 'ECS version this event conforms to. `ecs.version` is a required\nfield and must exist in all events.\n\nWhen querying across multiple indices -- which may conform to slightly different\nECS versions -- this field lets integrations adjust to the schema version\nof the events.', - example: '1.0.0', - }, - ], - }, - { - name: 'error', - title: 'Error', - group: 2, - description: - 'These fields can represent errors of any kind.\n\nUse them for errors that happen while fetching events or in cases where the\nevent itself contains an error.', - type: 'group', - fields: [ - { - name: 'code', - level: 'core', - type: 'keyword', - description: 'Error code describing the error.', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - description: 'Unique identifier for the error.', - }, - { - name: 'message', - level: 'core', - type: 'text', - description: 'Error message.', - }, - ], - }, - { - name: 'event', - title: 'Event', - group: 2, - description: - 'The event fields are used for context information about the log\nor metric event itself.\n\nA log is defined as an event containing details of something that happened.\nLog events must include the time at which the thing happened. Examples of log\nevents include a process starting on a host, a network packet being sent from\na source to a destination, or a network connection between a client and a server\nbeing initiated or closed. A metric is defined as an event containing one or\nmore numerical or categorical measurements and the time at which the measurement\nwas taken. Examples of metric events include memory pressure measured on a host,\nor vulnerabilities measured on a scanned host.', - type: 'group', - fields: [ - { - name: 'action', - level: 'core', - type: 'keyword', - description: - 'The action captured by the event.\n\nThis describes the information in the event. It is more specific than `event.category`.\nExamples are `group-add`, `process-started`, `file-created`. The value is\nnormally defined by the implementer.', - example: 'user-password-change', - }, - { - name: 'category', - level: 'core', - type: 'keyword', - description: - 'Event category.\n\nThis contains high-level information about the contents of the event. It is\nmore generic than `event.action`, in the sense that typically a category contains\nmultiple actions. Warning: In future versions of ECS, we plan to provide a\nlist of acceptable values for this field, please use with caution.', - example: 'user-management', - }, - { - name: 'created', - level: 'core', - type: 'date', - description: - 'event.created contains the date/time when the event was first\nread by an agent, or by your pipeline.\n\nThis field is distinct from @timestamp in that @timestamp typically contain\nthe time extracted from the original event.\n\nIn most situations, these two timestamps will be slightly different. The difference\ncan be used to calculate the delay between your source generating an event,\nand the time when your agent first processed it. This can be used to monitor\nyour agents or pipelines ability to keep up with your event source.\n\nIn case the two timestamps are identical, @timestamp should be used.', - }, - { - name: 'dataset', - level: 'core', - type: 'keyword', - description: - 'Name of the dataset.\n\nThe concept of a `dataset` (fileset / metricset) is used in Beats as a subset\nof modules. It contains the information which is currently stored in metricset.name\nand metricset.module or fileset.name.', - example: 'stats', - }, - { - name: 'duration', - level: 'core', - type: 'long', - format: 'duration', - input_format: 'nanoseconds', - description: - 'Duration of the event in nanoseconds.\n\nIf event.start and event.end are known this value should be the difference\nbetween the end and start time.', - }, - { - name: 'end', - level: 'extended', - type: 'date', - description: - 'event.end contains the date when the event ended or when the activity\nwas last observed.', - }, - { - name: 'hash', - level: 'extended', - type: 'keyword', - description: - 'Hash (perhaps logstash fingerprint) of raw field to be able to\ndemonstrate log integrity.', - example: '123456789012345678901234567890ABCD', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - description: 'Unique ID to describe the event.', - example: '8a4f500d', - }, - { - name: 'kind', - level: 'extended', - type: 'keyword', - description: - 'The kind of the event.\n\nThis gives information about what type of information the event contains,\nwithout being specific to the contents of the event. Examples are `event`,\n`state`, `alarm`. Warning: In future versions of ECS, we plan to provide a\nlist of acceptable values for this field, please use with caution.', - example: 'state', - }, - { - name: 'module', - level: 'core', - type: 'keyword', - description: - 'Name of the module this data is coming from.\n\nThis information is coming from the modules used in Beats or Logstash.', - example: 'mysql', - }, - { - name: 'original', - level: 'core', - type: 'keyword', - description: - 'Raw text message of entire event. Used to demonstrate log integrity.\n\nThis field is not indexed and doc_values are disabled. It cannot be searched,\nbut it can be retrieved from `_source`.', - example: - 'Sep 19 08:26:10 host CEF:0|Security| threatmanager|1.0|100|\nworm successfully stopped|10|src=10.0.0.1 dst=2.1.2.2spt=1232', - }, - { - name: 'outcome', - level: 'extended', - type: 'keyword', - description: - 'The outcome of the event.\n\nIf the event describes an action, this fields contains the outcome of that\naction. Examples outcomes are `success` and `failure`. Warning: In future\nversions of ECS, we plan to provide a list of acceptable values for this field,\nplease use with caution.', - example: 'success', - }, - { - name: 'risk_score', - level: 'core', - type: 'float', - description: - "Risk score or priority of the event (e.g. security solutions).\nUse your system's original value here.", - }, - { - name: 'risk_score_norm', - level: 'extended', - type: 'float', - description: - 'Normalized risk score or priority of the event, on a scale of\n0 to 100.\n\nThis is mainly useful if you use more than one system that assigns risk scores,\nand you want to see a normalized value across all systems.', - }, - { - name: 'severity', - level: 'core', - type: 'long', - format: 'string', - description: - "Severity describes the original severity of the event. What the\ndifferent severity values mean can very different between use cases. It's\nup to the implementer to make sure severities are consistent across events.", - example: '7', - }, - { - name: 'start', - level: 'extended', - type: 'date', - description: - 'event.start contains the date when the event started or when the\nactivity was first observed.', - }, - { - name: 'timezone', - level: 'extended', - type: 'keyword', - description: - 'This field should be populated when the events timestamp does\nnot include timezone information already (e.g. default Syslog timestamps).\nIts optional otherwise.\n\nAcceptable timezone formats are: a canonical ID (e.g. "Europe/Amsterdam"),\nabbreviated (e.g. "EST") or an HH:mm differential (e.g. "-05:00").', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - description: - 'Reserved for future usage.\n\nPlease avoid using this field for user data.', - }, - ], - }, - { - name: 'file', - title: 'File', - group: 2, - description: - 'A file is defined as a set of information that has been created\non, or has existed on a filesystem.\n\nFile objects can be associated with host events, network events, and/or file\nevents (e.g., those produced by File Integrity Monitoring [FIM] products or\nservices). File fields provide details about the affected file associated with\nthe event or metric.', - type: 'group', - fields: [ - { - name: 'ctime', - level: 'extended', - type: 'date', - description: 'Last time file metadata changed.', - }, - { - name: 'device', - level: 'extended', - type: 'keyword', - description: 'Device that is the source of the file.', - }, - { - name: 'extension', - level: 'extended', - type: 'keyword', - description: 'File extension.\n\nThis should allow easy filtering by file extensions.', - example: 'png', - }, - { - name: 'gid', - level: 'extended', - type: 'keyword', - description: 'Primary group ID (GID) of the file.', - }, - { - name: 'group', - level: 'extended', - type: 'keyword', - description: 'Primary group name of the file.', - }, - { - name: 'inode', - level: 'extended', - type: 'keyword', - description: 'Inode representing the file in the filesystem.', - }, - { - name: 'mode', - level: 'extended', - type: 'keyword', - description: 'Mode of the file in octal representation.', - example: 416, - }, - { - name: 'mtime', - level: 'extended', - type: 'date', - description: 'Last time file content was modified.', - }, - { - name: 'owner', - level: 'extended', - type: 'keyword', - description: "File owner's username.", - }, - { - name: 'path', - level: 'extended', - type: 'keyword', - description: 'Path to the file.', - }, - { - name: 'size', - level: 'extended', - type: 'long', - description: 'File size in bytes (field is only added when `type` is `file`).', - }, - { - name: 'target_path', - level: 'extended', - type: 'keyword', - description: 'Target path for symlinks.', - }, - { - name: 'type', - level: 'extended', - type: 'keyword', - description: 'File type (file, dir, or symlink).', - }, - { - name: 'uid', - level: 'extended', - type: 'keyword', - description: 'The user ID (UID) or security identifier (SID) of the file owner.', - }, - ], - }, - { - name: 'geo', - title: 'Geo', - group: 2, - description: - 'Geo fields can carry data about a specific location related to an\nevent.\n\nThis geolocation information can be derived from techniques such as Geo IP,\nor be user-supplied.', - type: 'group', - fields: [ - { - name: 'city_name', - level: 'core', - type: 'keyword', - description: 'City name.', - example: 'Montreal', - }, - { - name: 'continent_name', - level: 'core', - type: 'keyword', - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'country_iso_code', - level: 'core', - type: 'keyword', - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'country_name', - level: 'core', - type: 'keyword', - description: 'Country name.', - example: 'Canada', - }, - { - name: 'location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'region_iso_code', - level: 'core', - type: 'keyword', - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'region_name', - level: 'core', - type: 'keyword', - description: 'Region name.', - example: 'Quebec', - }, - ], - }, - { - name: 'group', - title: 'Group', - group: 2, - description: - 'The group fields are meant to represent groups that are relevant\nto the event.', - type: 'group', - fields: [ - { - name: 'id', - level: 'extended', - type: 'keyword', - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - description: 'Name of the group.', - }, - ], - }, - { - name: 'host', - title: 'Host', - group: 2, - description: - 'A host is defined as a general computing instance.\n\nECS host.* fields should be populated with details about the host on which the\nevent happened, or from which the measurement was taken. Host types include\nhardware, virtual machines, Docker containers, and Kubernetes nodes.', - type: 'group', - fields: [ - { - name: 'architecture', - level: 'core', - type: 'keyword', - description: 'Operating system architecture.', - example: 'x86_64', - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'hostname', - level: 'core', - type: 'keyword', - description: - 'Hostname of the host.\n\nIt normally contains what the `hostname` command returns on the host machine.', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - description: - 'Unique host id.\n\nAs hostname is not always unique, use values that are meaningful in your environment.\n\nExample: The current usage of `beat.name`.', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: 'Host ip address.', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - description: 'Host mac address.', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - description: - 'Name of the host.\n\nIt can contain what `hostname` returns on Unix systems, the fully qualified\ndomain name, or a name specified by the user. The sender decides which value\nto use.', - }, - { - name: 'os.family', - level: 'extended', - type: 'keyword', - description: 'OS family (such as redhat, debian, freebsd, windows).', - example: 'debian', - }, - { - name: 'os.full', - level: 'extended', - type: 'keyword', - description: 'Operating system name, including the version or code name.', - example: 'Mac OS Mojave', - }, - { - name: 'os.kernel', - level: 'extended', - type: 'keyword', - description: 'Operating system kernel version as a raw string.', - example: '4.4.0-112-generic', - }, - { - name: 'os.name', - level: 'extended', - type: 'keyword', - description: 'Operating system name, without the version.', - example: 'Mac OS X', - }, - { - name: 'os.platform', - level: 'extended', - type: 'keyword', - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'os.version', - level: 'extended', - type: 'keyword', - description: 'Operating system version as a raw string.', - example: '10.14.1', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - description: - 'Type of host.\n\nFor Cloud providers this can be the machine type like `t2.medium`. If vm,\nthis could be the container, for example, or other information meaningful\nin your environment.', - }, - { - name: 'user.email', - level: 'extended', - type: 'keyword', - description: 'User email address.', - }, - { - name: 'user.full_name', - level: 'extended', - type: 'keyword', - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'user.group.id', - level: 'extended', - type: 'keyword', - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'user.group.name', - level: 'extended', - type: 'keyword', - description: 'Name of the group.', - }, - { - name: 'user.hash', - level: 'extended', - type: 'keyword', - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'user.id', - level: 'core', - type: 'keyword', - description: 'One or multiple unique identifiers of the user.', - }, - { - name: 'user.name', - level: 'core', - type: 'keyword', - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'http', - title: 'HTTP', - group: 2, - description: - 'Fields related to HTTP activity. Use the `url` field set to store\nthe url of the request.', - type: 'group', - fields: [ - { - name: 'request.body.bytes', - level: 'extended', - type: 'long', - format: 'bytes', - description: 'Size in bytes of the request body.', - example: 887, - }, - { - name: 'request.body.content', - level: 'extended', - type: 'keyword', - description: 'The full HTTP request body.', - example: 'Hello world', - }, - { - name: 'request.bytes', - level: 'extended', - type: 'long', - format: 'bytes', - description: 'Total size in bytes of the request (body and headers).', - example: 1437, - }, - { - name: 'request.method', - level: 'extended', - type: 'keyword', - description: - 'HTTP request method.\n\nThe field value must be normalized to lowercase for querying. See the documentation\nsection "Implementing ECS".', - example: 'get, post, put', - }, - { - name: 'request.referrer', - level: 'extended', - type: 'keyword', - description: 'Referrer for this HTTP request.', - example: 'https://blog.example.com/', - }, - { - name: 'response.body.bytes', - level: 'extended', - type: 'long', - format: 'bytes', - description: 'Size in bytes of the response body.', - example: 887, - }, - { - name: 'response.body.content', - level: 'extended', - type: 'keyword', - description: 'The full HTTP response body.', - example: 'Hello world', - }, - { - name: 'response.bytes', - level: 'extended', - type: 'long', - format: 'bytes', - description: 'Total size in bytes of the response (body and headers).', - example: 1437, - }, - { - name: 'response.status_code', - level: 'extended', - type: 'long', - format: 'string', - description: 'HTTP response status code.', - example: 404, - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - description: 'HTTP version.', - example: 1.1, - }, - ], - }, - { - name: 'log', - title: 'Log', - group: 2, - description: 'Fields which are specific to log events.', - type: 'group', - fields: [ - { - name: 'level', - level: 'core', - type: 'keyword', - description: - 'Original log level of the log event.\n\nSome examples are `warn`, `error`, `i`.', - example: 'err', - }, - { - name: 'original', - level: 'core', - type: 'keyword', - description: - 'This is the original log message and contains the full log message\nbefore splitting it up in multiple parts.\n\nIn contrast to the `message` field which can contain an extracted part of\nthe log message, this field contains the original, full log message. It can\nhave already some modifications applied like encoding or new lines removed\nto clean up the log message.\n\nThis field is not indexed and doc_values are disabled so it cant be queried\nbut the value can be retrieved from `_source`.', - example: 'Sep 19 08:26:10 localhost My log', - }, - ], - }, - { - name: 'network', - title: 'Network', - group: 2, - description: - 'The network is defined as the communication path over which a host\nor network event happens.\n\nThe network.* fields should be populated with details about the network activity\nassociated with an event.', - type: 'group', - fields: [ - { - name: 'application', - level: 'extended', - type: 'keyword', - description: - 'A name given to an application level protocol. This can be arbitrarily\nassigned for things like microservices, but also apply to things like skype,\nicq, facebook, twitter. This would be used in situations where the vendor\nor service can be decoded such as from the source/dest IP owners, ports, or\nwire format.\n\nThe field value must be normalized to lowercase for querying. See the documentation\nsection "Implementing ECS".', - example: 'aim', - }, - { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - description: - 'Total bytes transferred in both directions.\n\nIf `source.bytes` and `destination.bytes` are known, `network.bytes` is their\nsum.', - example: 368, - }, - { - name: 'community_id', - level: 'extended', - type: 'keyword', - description: - 'A hash of source and destination IPs and ports, as well as the\nprotocol used in a communication. This is a tool-agnostic standard to identify\nflows.\n\nLearn more at https://github.com/corelight/community-id-spec.', - example: '1:hO+sN4H+MG5MY/8hIrXPqc4ZQz0=', - }, - { - name: 'direction', - level: 'core', - type: 'keyword', - description: - "Direction of the network traffic. Recommended values are: * inbound * outbound * internal * external * unknown When mapping events from a host-based monitoring context, populate this field from the host's point of view. When mapping events from a network or perimeter-based monitoring context, populate this field from the point of view of your network perimeter.", - example: 'inbound', - }, - { - name: 'forwarded_ip', - level: 'core', - type: 'ip', - description: 'Host IP address when the source IP address is the proxy.', - example: '192.1.1.2', - }, - { - name: 'iana_number', - level: 'extended', - type: 'keyword', - description: - 'IANA Protocol Number (https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml).\nStandardized list of protocols. This aligns well with NetFlow and sFlow related\nlogs which use the IANA Protocol Number.', - example: 6, - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - description: 'Name given by operators to sections of their network.', - example: 'Guest Wifi', - }, - { - name: 'packets', - level: 'core', - type: 'long', - description: - 'Total packets transferred in both directions.\n\nIf `source.packets` and `destination.packets` are known, `network.packets`\nis their sum.', - example: 24, - }, - { - name: 'protocol', - level: 'core', - type: 'keyword', - description: - 'L7 Network protocol name. ex. http, lumberjack, transport protocol.\n\nThe field value must be normalized to lowercase for querying. See the documentation\nsection "Implementing ECS".', - example: 'http', - }, - { - name: 'transport', - level: 'core', - type: 'keyword', - description: - 'Same as network.iana_number, but instead using the Keyword name\nof the transport layer (udp, tcp, ipv6-icmp, etc.)\n\nThe field value must be normalized to lowercase for querying. See the documentation\nsection "Implementing ECS".', - example: 'tcp', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - description: - 'In the OSI Model this would be the Network Layer. ipv4, ipv6,\nipsec, pim, etc\n\nThe field value must be normalized to lowercase for querying. See the documentation\nsection "Implementing ECS".', - example: 'ipv4', - }, - ], - }, - { - name: 'observer', - title: 'Observer', - group: 2, - description: - 'An observer is defined as a special network, security, or application\ndevice used to detect, observe, or create network, security, or application-related\nevents and metrics.\n\nThis could be a custom hardware appliance or a server that has been configured\nto run special network, security, or application software. Examples include\nfirewalls, intrusion detection/prevention systems, network monitoring sensors,\nweb application firewalls, data loss prevention systems, and APM servers. The\nobserver.* fields shall be populated with details of the system, if any, that\ndetects, observes and/or creates a network, security, or application event or\nmetric. Message queues and ETL components used in processing events or metrics\nare not considered observers in ECS.', - type: 'group', - fields: [ - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'hostname', - level: 'core', - type: 'keyword', - description: 'Hostname of the observer.', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: 'IP address of the observer.', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - description: 'MAC address of the observer', - }, - { - name: 'os.family', - level: 'extended', - type: 'keyword', - description: 'OS family (such as redhat, debian, freebsd, windows).', - example: 'debian', - }, - { - name: 'os.full', - level: 'extended', - type: 'keyword', - description: 'Operating system name, including the version or code name.', - example: 'Mac OS Mojave', - }, - { - name: 'os.kernel', - level: 'extended', - type: 'keyword', - description: 'Operating system kernel version as a raw string.', - example: '4.4.0-112-generic', - }, - { - name: 'os.name', - level: 'extended', - type: 'keyword', - description: 'Operating system name, without the version.', - example: 'Mac OS X', - }, - { - name: 'os.platform', - level: 'extended', - type: 'keyword', - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'os.version', - level: 'extended', - type: 'keyword', - description: 'Operating system version as a raw string.', - example: '10.14.1', - }, - { - name: 'serial_number', - level: 'extended', - type: 'keyword', - description: 'Observer serial number.', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - description: - 'The type of the observer the data is coming from.\n\nThere is no predefined list of observer types. Some examples are `forwarder`,\n`firewall`, `ids`, `ips`, `proxy`, `poller`, `sensor`, `APM server`.', - example: 'firewall', - }, - { - name: 'vendor', - level: 'core', - type: 'keyword', - description: 'observer vendor information.', - }, - { - name: 'version', - level: 'core', - type: 'keyword', - description: 'Observer version.', - }, - ], - }, - { - name: 'organization', - title: 'Organization', - group: 2, - description: - 'The organization fields enrich data with information about the company\nor entity the data is associated with.\n\nThese fields help you arrange or filter data stored in an index by one or multiple\norganizations.', - type: 'group', - fields: [ - { - name: 'id', - level: 'extended', - type: 'keyword', - description: 'Unique identifier for the organization.', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - description: 'Organization name.', - }, - ], - }, - { - name: 'os', - title: 'Operating System', - group: 2, - description: 'The OS fields contain information about the operating system.', - type: 'group', - fields: [ - { - name: 'family', - level: 'extended', - type: 'keyword', - description: 'OS family (such as redhat, debian, freebsd, windows).', - example: 'debian', - }, - { - name: 'full', - level: 'extended', - type: 'keyword', - description: 'Operating system name, including the version or code name.', - example: 'Mac OS Mojave', - }, - { - name: 'kernel', - level: 'extended', - type: 'keyword', - description: 'Operating system kernel version as a raw string.', - example: '4.4.0-112-generic', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - description: 'Operating system name, without the version.', - example: 'Mac OS X', - }, - { - name: 'platform', - level: 'extended', - type: 'keyword', - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - description: 'Operating system version as a raw string.', - example: '10.14.1', - }, - ], - }, - { - name: 'process', - title: 'Process', - group: 2, - description: - 'These fields contain information about a process.\n\nThese fields can help you correlate metrics information with a process id/name\nfrom a log message. The `process.pid` often stays in the metric itself and\nis copied to the global field for correlation.', - type: 'group', - fields: [ - { - name: 'args', - level: 'extended', - type: 'keyword', - description: - 'Array of process arguments.\n\nMay be filtered to protect sensitive information.', - example: ['ssh', '-l', 'user', '10.0.0.16'], - }, - { - name: 'executable', - level: 'extended', - type: 'keyword', - description: 'Absolute path to the process executable.', - example: '/usr/bin/ssh', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - description: 'Process name.\n\nSometimes called program name or similar.', - example: 'ssh', - }, - { - name: 'pid', - level: 'core', - type: 'long', - format: 'string', - description: 'Process id.', - example: 4242, - }, - { - name: 'ppid', - level: 'extended', - type: 'long', - format: 'string', - description: "Parent process' pid.", - example: 4241, - }, - { - name: 'start', - level: 'extended', - type: 'date', - description: 'The time the process started.', - example: '2016-05-23T08:05:34.853Z', - }, - { - name: 'thread.id', - level: 'extended', - type: 'long', - format: 'string', - description: 'Thread ID.', - example: 4242, - }, - { - name: 'title', - level: 'extended', - type: 'keyword', - description: - 'Process title.\n\nThe proctitle, some times the same as process name. Can also be different:\nfor example a browser setting its title to the web page currently opened.', - }, - { - name: 'working_directory', - level: 'extended', - type: 'keyword', - description: 'The working directory of the process.', - example: '/home/alice', - }, - ], - }, - { - name: 'related', - title: 'Related', - group: 2, - description: - 'This field set is meant to facilitate pivoting around a piece of\ndata.\n\nSome pieces of information can be seen in many places in an ECS event. To facilitate\nsearching for them, store an array of all seen values to their corresponding\nfield in `related.`.\n\nA concrete example is IP addresses, which can be under host, observer, source,\ndestination, client, server, and network.forwarded_ip. If you append all IPs\nto `related.ip`, you can then search for a given IP trivially, no matter where\nit appeared, by querying `related.ip:a.b.c.d`.', - type: 'group', - fields: [ - { - name: 'ip', - level: 'extended', - type: 'ip', - description: 'All of the IPs seen on your event.', - }, - ], - }, - { - name: 'server', - title: 'Server', - group: 2, - description: - 'A Server is defined as the responder in a network connection for\nevents regarding sessions, connections, or bidirectional flow records.\n\nFor TCP events, the server is the receiver of the initial SYN packet(s) of the\nTCP connection. For other protocols, the server is generally the responder in\nthe network transaction. Some systems actually use the term "responder" to refer\nthe server in TCP connections. The server fields describe details about the\nsystem acting as the server in the network event. Server fields are usually\npopulated in conjunction with client fields. Server fields are generally not\npopulated for packet-level events.\n\nClient / server representations can add semantic context to an exchange, which\nis helpful to visualize the data in certain situations. If your context falls\nin that category, you should still ensure that source and destination are filled\nappropriately.', - type: 'group', - fields: [ - { - name: 'address', - level: 'extended', - type: 'keyword', - description: - 'Some event server addresses are defined ambiguously. The event\nwill sometimes list an IP, a domain or a unix socket. You should always store\nthe raw address in the `.address` field.\n\nThen it should be duplicated to `.ip` or `.domain`, depending on which one\nit is.', - }, - { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - description: 'Bytes sent from the server to the client.', - example: 184, - }, - { - name: 'domain', - level: 'core', - type: 'keyword', - description: 'Server domain.', - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: - 'IP address of the server.\n\nCan be one or multiple IPv4 or IPv6 addresses.', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - description: 'MAC address of the server.', - }, - { - name: 'packets', - level: 'core', - type: 'long', - description: 'Packets sent from the server to the client.', - example: 12, - }, - { - name: 'port', - level: 'core', - type: 'long', - format: 'string', - description: 'Port of the server.', - }, - { - name: 'user.email', - level: 'extended', - type: 'keyword', - description: 'User email address.', - }, - { - name: 'user.full_name', - level: 'extended', - type: 'keyword', - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'user.group.id', - level: 'extended', - type: 'keyword', - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'user.group.name', - level: 'extended', - type: 'keyword', - description: 'Name of the group.', - }, - { - name: 'user.hash', - level: 'extended', - type: 'keyword', - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'user.id', - level: 'core', - type: 'keyword', - description: 'One or multiple unique identifiers of the user.', - }, - { - name: 'user.name', - level: 'core', - type: 'keyword', - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'service', - title: 'Service', - group: 2, - description: - 'The service fields describe the service for or from which the data\nwas collected.\n\nThese fields help you find and correlate logs for a specific service and version.', - type: 'group', - fields: [ - { - name: 'ephemeral_id', - level: 'extended', - type: 'keyword', - description: - 'Ephemeral identifier of this service (if one exists).\n\nThis id normally changes across restarts, but `service.id` does not.', - example: '8a4f500f', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - description: - 'Unique identifier of the running service.\n\nThis id should uniquely identify this service. This makes it possible to correlate\nlogs and metrics for one specific service.\n\nExample: If you are experiencing issues with one redis instance, you can filter\non that id to see metrics and logs for that single instance.', - example: 'd37e5ebfe0ae6c4972dbe9f0174a1637bb8247f6', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - description: - 'Name of the service data is collected from.\n\nThe name of the service is normally user given. This allows if two instances\nof the same service are running on the same machine they can be differentiated\nby the `service.name`.\n\nAlso it allows for distributed services that run on multiple hosts to correlate\nthe related instances based on the name.\n\nIn the case of Elasticsearch the service.name could contain the cluster name.\nFor Beats the service.name is by default a copy of the `service.type` field\nif no name is specified.', - example: 'elasticsearch-metrics', - }, - { - name: 'state', - level: 'core', - type: 'keyword', - description: 'Current state of the service.', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - description: - 'The type of the service data is collected from.\n\nThe type can be used to group and correlate logs and metrics from one service\ntype.\n\nExample: If logs or metrics are collected from Elasticsearch, `service.type`\nwould be `elasticsearch`.', - example: 'elasticsearch', - }, - { - name: 'version', - level: 'core', - type: 'keyword', - description: - 'Version of the service the data was collected from.\n\nThis allows to look at a data set only for a specific version of a service.', - example: '3.2.4', - }, - ], - }, - { - name: 'source', - title: 'Source', - group: 2, - description: - 'Source fields describe details about the source of a packet/event.\n\nSource fields are usually populated in conjunction with destination fields.', - type: 'group', - fields: [ - { - name: 'address', - level: 'extended', - type: 'keyword', - description: - 'Some event source addresses are defined ambiguously. The event\nwill sometimes list an IP, a domain or a unix socket. You should always store\nthe raw address in the `.address` field.\n\nThen it should be duplicated to `.ip` or `.domain`, depending on which one\nit is.', - }, - { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - description: 'Bytes sent from the source to the destination.', - example: 184, - }, - { - name: 'domain', - level: 'core', - type: 'keyword', - description: 'Source domain.', - }, - { - name: 'geo.city_name', - level: 'core', - type: 'keyword', - description: 'City name.', - example: 'Montreal', - }, - { - name: 'geo.continent_name', - level: 'core', - type: 'keyword', - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'geo.country_iso_code', - level: 'core', - type: 'keyword', - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'geo.country_name', - level: 'core', - type: 'keyword', - description: 'Country name.', - example: 'Canada', - }, - { - name: 'geo.location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'geo.name', - level: 'extended', - type: 'keyword', - description: - 'User-defined description of a location, at the level of granularity\nthey care about.\n\nCould be the name of their data centers, the floor number, if this describes\na local physical entity, city names.\n\nNot typically used in automated geolocation.', - example: 'boston-dc', - }, - { - name: 'geo.region_iso_code', - level: 'core', - type: 'keyword', - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'geo.region_name', - level: 'core', - type: 'keyword', - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: - 'IP address of the source.\n\nCan be one or multiple IPv4 or IPv6 addresses.', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - description: 'MAC address of the source.', - }, - { - name: 'packets', - level: 'core', - type: 'long', - description: 'Packets sent from the source to the destination.', - example: 12, - }, - { - name: 'port', - level: 'core', - type: 'long', - format: 'string', - description: 'Port of the source.', - }, - { - name: 'user.email', - level: 'extended', - type: 'keyword', - description: 'User email address.', - }, - { - name: 'user.full_name', - level: 'extended', - type: 'keyword', - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'user.group.id', - level: 'extended', - type: 'keyword', - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'user.group.name', - level: 'extended', - type: 'keyword', - description: 'Name of the group.', - }, - { - name: 'user.hash', - level: 'extended', - type: 'keyword', - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'user.id', - level: 'core', - type: 'keyword', - description: 'One or multiple unique identifiers of the user.', - }, - { - name: 'user.name', - level: 'core', - type: 'keyword', - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'url', - title: 'URL', - group: 2, - description: - 'URL fields provide support for complete or partial URLs, and supports\nthe breaking down into scheme, domain, path, and so on.', - type: 'group', - fields: [ - { - name: 'domain', - level: 'extended', - type: 'keyword', - description: - 'Domain of the url, such as "www.elastic.co".\n\nIn some cases a URL may refer to an IP and/or port directly, without a domain\nname. In this case, the IP address would go to the `domain` field.', - example: 'www.elastic.co', - }, - { - name: 'fragment', - level: 'extended', - type: 'keyword', - description: - 'Portion of the url after the `#`, such as "top".\n\nThe `#` is not part of the fragment.', - }, - { - name: 'full', - level: 'extended', - type: 'keyword', - description: - 'If full URLs are important to your use case, they should be stored\nin `url.full`, whether this field is reconstructed or present in the event\nsource.', - example: 'https://www.elastic.co:443/search?q=elasticsearch#top', - }, - { - name: 'original', - level: 'extended', - type: 'keyword', - description: - 'Unmodified original url as seen in the event source.\n\nNote that in network monitoring, the observed URL may be a full URL, whereas\nin access logs, the URL is often just represented as a path.\n\nThis field is meant to represent the URL as it was observed, complete or not.', - example: - 'https://www.elastic.co:443/search?q=elasticsearch#top or /search?q=elasticsearch', - }, - { - name: 'password', - level: 'extended', - type: 'keyword', - description: 'Password of the request.', - }, - { - name: 'path', - level: 'extended', - type: 'keyword', - description: 'Path of the request, such as "/search".', - }, - { - name: 'port', - level: 'extended', - type: 'long', - format: 'string', - description: 'Port of the request, such as 443.', - example: 443, - }, - { - name: 'query', - level: 'extended', - type: 'keyword', - description: - 'The query field describes the query string of the request, such\nas "q=elasticsearch".\n\nThe `?` is excluded from the query string. If a URL contains no `?`, there\nis no query field. If there is a `?` but no query, the query field exists\nwith an empty string. The `exists` query can be used to differentiate between\nthe two cases.', - }, - { - name: 'scheme', - level: 'extended', - type: 'keyword', - description: - 'Scheme of the request, such as "https".\n\nNote: The `:` is not part of the scheme.', - example: 'https', - }, - { - name: 'username', - level: 'extended', - type: 'keyword', - description: 'Username of the request.', - }, - ], - }, - { - name: 'user', - title: 'User', - group: 2, - description: - 'The user fields describe information about the user that is relevant\nto the event.\n\nFields can have one entry or multiple entries. If a user has more than one id,\nprovide an array that includes all of them.', - type: 'group', - fields: [ - { - name: 'email', - level: 'extended', - type: 'keyword', - description: 'User email address.', - }, - { - name: 'full_name', - level: 'extended', - type: 'keyword', - description: "User's full name, if available.", - example: 'Albert Einstein', - }, - { - name: 'group.id', - level: 'extended', - type: 'keyword', - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'group.name', - level: 'extended', - type: 'keyword', - description: 'Name of the group.', - }, - { - name: 'hash', - level: 'extended', - type: 'keyword', - description: - 'Unique user hash to correlate information for a user in anonymized\nform.\n\nUseful if `user.id` or `user.name` contain confidential information and cannot\nbe used.', - }, - { - name: 'id', - level: 'core', - type: 'keyword', - description: 'One or multiple unique identifiers of the user.', - }, - { - name: 'name', - level: 'core', - type: 'keyword', - description: 'Short name or login of the user.', - example: 'albert', - }, - ], - }, - { - name: 'user_agent', - title: 'User agent', - group: 2, - description: - 'The user_agent fields normally come from a browser request.\n\nThey often show up in web service logs coming from the parsed user agent string.', - type: 'group', - fields: [ - { - name: 'device.name', - level: 'extended', - type: 'keyword', - description: 'Name of the device.', - example: 'iPhone', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - description: 'Name of the user agent.', - example: 'Safari', - }, - { - name: 'original', - level: 'extended', - type: 'keyword', - description: 'Unparsed version of the user_agent.', - example: - 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15\n(KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1', - }, - { - name: 'os.family', - level: 'extended', - type: 'keyword', - description: 'OS family (such as redhat, debian, freebsd, windows).', - example: 'debian', - }, - { - name: 'os.full', - level: 'extended', - type: 'keyword', - description: 'Operating system name, including the version or code name.', - example: 'Mac OS Mojave', - }, - { - name: 'os.kernel', - level: 'extended', - type: 'keyword', - description: 'Operating system kernel version as a raw string.', - example: '4.4.0-112-generic', - }, - { - name: 'os.name', - level: 'extended', - type: 'keyword', - description: 'Operating system name, without the version.', - example: 'Mac OS X', - }, - { - name: 'os.platform', - level: 'extended', - type: 'keyword', - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'os.version', - level: 'extended', - type: 'keyword', - description: 'Operating system version as a raw string.', - example: '10.14.1', - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - description: 'Version of the user agent.', - example: 12, - }, - ], - }, - ], - }, - { - key: 'beat', - title: 'Beat', - description: 'Contains common beat fields available in all event types.\n', - fields: [ - { - name: 'agent.hostname', - type: 'keyword', - description: 'Hostname of the agent.', - }, - { - name: 'beat.timezone', - type: 'alias', - path: 'event.timezone', - migration: true, - }, - { - name: 'fields', - type: 'object', - object_type: 'keyword', - description: 'Contains user configurable fields.\n', - }, - { - name: 'error', - type: 'group', - description: 'Error fields containing additional info in case of errors.\n', - fields: [ - { - name: 'type', - type: 'keyword', - description: 'Error type.\n', - }, - ], - }, - { - name: 'beat.name', - type: 'alias', - path: 'host.name', - migration: true, - }, - { - name: 'beat.hostname', - type: 'alias', - path: 'agent.hostname', - migration: true, - }, - { - name: 'timeseries.instance', - type: 'keyword', - description: 'Time series instance id', - }, - ], - }, - { - key: 'cloud', - title: 'Cloud provider metadata', - description: 'Metadata from cloud providers added by the add_cloud_metadata processor.\n', - fields: [ - { - name: 'cloud.project.id', - example: 'project-x', - description: 'Name of the project in Google Cloud.\n', - }, - { - name: 'meta.cloud.provider', - type: 'alias', - path: 'cloud.provider', - migration: true, - }, - { - name: 'meta.cloud.instance_id', - type: 'alias', - path: 'cloud.instance.id', - migration: true, - }, - { - name: 'meta.cloud.instance_name', - type: 'alias', - path: 'cloud.instance.name', - migration: true, - }, - { - name: 'meta.cloud.machine_type', - type: 'alias', - path: 'cloud.machine.type', - migration: true, - }, - { - name: 'meta.cloud.availability_zone', - type: 'alias', - path: 'cloud.availability_zone', - migration: true, - }, - { - name: 'meta.cloud.project_id', - type: 'alias', - path: 'cloud.project.id', - migration: true, - }, - { - name: 'meta.cloud.region', - type: 'alias', - path: 'cloud.region', - migration: true, - }, - ], - }, - { - key: 'docker', - title: 'Docker', - description: 'Docker stats collected from Docker.\n', - short_config: false, - anchor: 'docker-processor', - fields: [ - { - name: 'docker', - type: 'group', - fields: [ - { - name: 'container.id', - type: 'alias', - path: 'container.id', - migration: true, - }, - { - name: 'container.image', - type: 'alias', - path: 'container.image.name', - migration: true, - }, - { - name: 'container.name', - type: 'alias', - path: 'container.name', - migration: true, - }, - { - name: 'container.labels', - type: 'object', - object_type: 'keyword', - description: 'Image labels.\n', - }, - ], - }, - ], - }, - { - key: 'host', - title: 'Host', - description: 'Info collected for the host machine.\n', - anchor: 'host-processor', - fields: [ - { - name: 'host', - type: 'group', - fields: [ - { - name: 'containerized', - type: 'boolean', - description: 'If the host is a container.\n', - }, - { - name: 'os.build', - type: 'keyword', - example: '18D109', - description: 'OS build information.\n', - }, - { - name: 'os.codename', - type: 'keyword', - example: 'stretch', - description: 'OS codename, if any.\n', - }, - ], - }, - ], - }, - { - key: 'kubernetes', - title: 'Kubernetes', - description: 'Kubernetes metadata added by the kubernetes processor\n', - short_config: false, - anchor: 'kubernetes-processor', - fields: [ - { - name: 'kubernetes', - type: 'group', - fields: [ - { - name: 'pod.name', - type: 'keyword', - description: 'Kubernetes pod name\n', - }, - { - name: 'pod.uid', - type: 'keyword', - description: 'Kubernetes Pod UID\n', - }, - { - name: 'namespace', - type: 'keyword', - description: 'Kubernetes namespace\n', - }, - { - name: 'node.name', - type: 'keyword', - description: 'Kubernetes node name\n', - }, - { - name: 'labels', - type: 'object', - description: 'Kubernetes labels map\n', - }, - { - name: 'annotations', - type: 'object', - description: 'Kubernetes annotations map\n', - }, - { - name: 'replicaset.name', - type: 'keyword', - description: 'Kubernetes replicaset name\n', - }, - { - name: 'deployment.name', - type: 'keyword', - description: 'Kubernetes deployment name\n', - }, - { - name: 'statefulset.name', - type: 'keyword', - description: 'Kubernetes statefulset name\n', - }, - { - name: 'container.name', - type: 'keyword', - description: 'Kubernetes container name\n', - }, - { - name: 'container.image', - type: 'keyword', - description: 'Kubernetes container image\n', - }, - ], - }, - ], - }, - { - key: 'process', - title: 'Process', - description: 'Process metadata fields\n', - fields: [ - { - name: 'process', - type: 'group', - fields: [ - { - name: 'exe', - type: 'alias', - path: 'process.executable', - migration: true, - }, - ], - }, - ], - }, - { - key: 'jolokia-autodiscover', - title: 'Jolokia Discovery autodiscover provider', - description: 'Metadata from Jolokia Discovery added by the jolokia provider.\n', - fields: [ - { - name: 'jolokia.agent.version', - type: 'keyword', - description: 'Version number of jolokia agent.\n', - }, - { - name: 'jolokia.agent.id', - type: 'keyword', - description: - 'Each agent has a unique id which can be either provided during startup of the agent in form of a configuration parameter or being autodetected. If autodected, the id has several parts: The IP, the process id, hashcode of the agent and its type.\n', - }, - { - name: 'jolokia.server.product', - type: 'keyword', - description: 'The container product if detected.\n', - }, - { - name: 'jolokia.server.version', - type: 'keyword', - description: "The container's version (if detected).\n", - }, - { - name: 'jolokia.server.vendor', - type: 'keyword', - description: 'The vendor of the container the agent is running in.\n', - }, - { - name: 'jolokia.url', - type: 'keyword', - description: 'The URL how this agent can be contacted.\n', - }, - { - name: 'jolokia.secured', - type: 'boolean', - description: 'Whether the agent was configured for authentication or not.\n', - }, - ], - }, - { - key: 'winlog', - title: 'Windows Event Log fields emitted by Winlogbeat', - description: 'Fields from the Windows Event Log.\n', - fields: [ - { - name: 'log.file.path', - type: 'keyword', - required: false, - description: - 'The name of the file the event was read from when Winlogbeat is reading directly from an .evtx file.\n', - }, - { - name: 'event.code', - type: 'keyword', - required: false, - description: 'The code for this log message (Windows event ID).\n', - }, - { - name: 'event.original', - description: - 'The raw XML representation of the event obtained from Windows. This field is only available on operating systems supporting the Windows Event Log API (Microsoft Windows Vista and newer). This field is not included by default and must be enabled by setting `include_xml: true` as a configuration option for an individual event log.\nThe XML representation of the event is useful for troubleshooting purposes. The data in the fields reported by Winlogbeat can be compared to the data in the XML to diagnose problems.\n', - }, - { - name: 'winlog', - type: 'group', - description: 'All fields specific to the Windows Event Log are defined here.\n', - fields: [ - { - name: 'api', - required: true, - description: - 'The event log API type used to read the record. The possible values are "wineventlog" for the Windows Event Log API or "eventlogging" for the Event Logging API.\nThe Event Logging API was designed for Windows Server 2003 or Windows 2000 operating systems. In Windows Vista, the event logging infrastructure was redesigned. On Windows Vista or later operating systems, the Windows Event Log API is used. Winlogbeat automatically detects which API to use for reading event logs.\n', - }, - { - name: 'activity_id', - type: 'keyword', - required: false, - description: - 'A globally unique identifier that identifies the current activity. The events that are published with this identifier are part of the same activity.\n', - }, - { - name: 'computer_name', - type: 'keyword', - required: true, - description: - 'The name of the computer that generated the record. When using Windows event forwarding, this name can differ from `agent.hostname`.\n', - }, - { - name: 'event_data', - type: 'object', - object_type: 'keyword', - required: false, - description: - 'The event-specific data. This field is mutually exclusive with `user_data`. If you are capturing event data on versions prior to Windows Vista, the parameters in `event_data` are named `param1`, `param2`, and so on, because event log parameters are unnamed in earlier versions of Windows.\n', - }, - { - name: 'event_id', - type: 'keyword', - required: true, - description: - 'The event identifier. The value is specific to the source of the event.\n', - }, - { - name: 'keywords', - type: 'keyword', - required: false, - description: 'The keywords are used to classify an event.\n', - }, - { - name: 'channel', - type: 'keyword', - required: true, - description: - 'The name of the channel from which this record was read. This value is one of the names from the `event_logs` collection in the configuration.\n', - }, - { - name: 'record_id', - type: 'keyword', - required: true, - description: - 'The record ID of the event log record. The first record written to an event log is record number 1, and other records are numbered sequentially. If the record number reaches the maximum value (2^32^ for the Event Logging API and 2^64^ for the Windows Event Log API), the next record number will be 0.\n', - }, - { - name: 'related_activity_id', - type: 'keyword', - required: false, - description: - 'A globally unique identifier that identifies the activity to which control was transferred to. The related events would then have this identifier as their `activity_id` identifier.\n', - }, - { - name: 'opcode', - type: 'keyword', - required: false, - description: - 'The opcode defined in the event. Task and opcode are typically used to identify the location in the application from where the event was logged.\n', - }, - { - name: 'provider_guid', - type: 'keyword', - required: false, - description: - 'A globally unique identifier that identifies the provider that logged the event.\n', - }, - { - name: 'process.pid', - type: 'long', - required: false, - description: 'The process_id of the Client Server Runtime Process.\n', - }, - { - name: 'provider_name', - type: 'keyword', - required: true, - description: - 'The source of the event log record (the application or service that logged the record).\n', - }, - { - name: 'task', - type: 'keyword', - required: false, - description: - 'The task defined in the event. Task and opcode are typically used to identify the location in the application from where the event was logged. The category used by the Event Logging API (on pre Windows Vista operating systems) is written to this field.\n', - }, - { - name: 'process.thread.id', - type: 'long', - required: false, - }, - { - name: 'user_data', - type: 'object', - object_type: 'keyword', - required: false, - description: - 'The event specific data. This field is mutually exclusive with `event_data`.\n', - }, - { - name: 'user.identifier', - type: 'keyword', - required: false, - example: 'S-1-5-21-3541430928-2051711210-1391384369-1001', - description: - 'The Windows security identifier (SID) of the account associated with this event.\n\nIf Winlogbeat cannot resolve the SID to a name, then the `user.name`, `user.domain`, and `user.type` fields will be omitted from the event. If you discover Winlogbeat not resolving SIDs, review the log for clues as to what the problem may be.\n', - }, - { - name: 'user.domain', - type: 'keyword', - required: false, - description: 'The domain that the account associated with this event is a member of.\n', - }, - { - name: 'user.type', - type: 'keyword', - required: false, - description: 'The type of account associated with this event.\n', - }, - { - name: 'version', - type: 'long', - required: false, - description: "The version number of the event's definition.", - }, - ], - }, - ], - }, - { - key: 'eventlog', - title: 'Event log record', - description: 'Contains data from a Windows event log record.\n', - fields: [ - { - name: 'type', - type: 'alias', - path: 'winlog.api', - migration: true, - }, - { - name: 'activity_id', - type: 'alias', - path: 'winlog.activity_id', - migration: true, - }, - { - name: 'computer_name', - type: 'alias', - path: 'winlog.computer_name', - migration: true, - }, - { - name: 'event_id', - type: 'alias', - path: 'winlog.event_id', - migration: true, - }, - { - name: 'keywords', - type: 'alias', - path: 'winlog.keywords', - migration: true, - }, - { - name: 'log_name', - type: 'alias', - path: 'winlog.channel', - migration: true, - }, - { - name: 'message_error', - type: 'alias', - path: 'error.message', - migration: true, - }, - { - name: 'record_number', - type: 'alias', - path: 'winlog.record_id', - migration: true, - }, - { - name: 'related_activity_id', - type: 'alias', - path: 'winlog.related_activity_id', - migration: true, - }, - { - name: 'opcode', - type: 'alias', - path: 'winlog.opcode', - migration: true, - }, - { - name: 'provider_guid', - type: 'alias', - path: 'winlog.provider_guid', - migration: true, - }, - { - name: 'process_id', - type: 'alias', - path: 'winlog.process.pid', - migration: true, - }, - { - name: 'source_name', - type: 'alias', - path: 'winlog.provider_name', - migration: true, - }, - { - name: 'task', - type: 'alias', - path: 'winlog.task', - migration: true, - }, - { - name: 'thread_id', - type: 'alias', - path: 'winlog.process.thread.id', - migration: true, - }, - { - name: 'user.identifier', - type: 'alias', - path: 'winlog.user.identifier', - migration: true, - }, - { - name: 'user.domain', - type: 'alias', - path: 'winlog.user.domain', - migration: true, - }, - { - name: 'user.type', - type: 'alias', - path: 'winlog.user.type', - migration: true, - }, - { - name: 'version', - type: 'alias', - path: 'winlog.version', - migration: true, - }, - { - name: 'xml', - type: 'alias', - path: 'event.original', - migration: true, - }, - ], - }, -]; diff --git a/x-pack/plugins/security_solution/server/utils/beat_schema/fields.ts b/x-pack/plugins/security_solution/server/utils/beat_schema/fields.ts new file mode 100644 index 0000000000000..e61b9ae008a62 --- /dev/null +++ b/x-pack/plugins/security_solution/server/utils/beat_schema/fields.ts @@ -0,0 +1,36118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { BeatFields } from '../../../common/search_strategy/index_fields'; + +/* eslint-disable @typescript-eslint/naming-convention */ +export const fieldsBeat: BeatFields = { + _id: { + category: 'base', + description: 'Each document has an _id that uniquely identifies it', + example: 'Y-6TfmcB0WOhS6qyMv3s', + name: '_id', + type: 'keyword', + }, + _index: { + category: 'base', + description: + 'An index is like a ‘database’ in a relational database. It has a mapping which defines multiple types. An index is a logical namespace which maps to one or more primary shards and can have zero or more replica shards.', + example: 'auditbeat-8.0.0-2019.02.19-000001', + name: '_index', + type: 'keyword', + }, + '@timestamp': { + category: 'base', + description: + 'Date/time when the event originated. This is the date/time extracted from the event, typically representing when the event was generated by the source. If the event source has no original timestamp, this value is typically populated by the first time the event was received by the pipeline. Required field for all events.', + example: '2016-05-23T08:05:34.853Z', + name: '@timestamp', + type: 'date', + }, + labels: { + category: 'base', + description: + 'Custom key/value pairs. Can be used to add meta information to events. Should not contain nested objects. All values are stored as keyword. Example: `docker` and `k8s` labels.', + example: '{"application": "foo-bar", "env": "production"}', + name: 'labels', + type: 'object', + }, + message: { + category: 'base', + description: + 'For log events the message field contains the log message, optimized for viewing in a log viewer. For structured logs without an original message field, other fields can be concatenated to form a human-readable summary of the event. If multiple messages exist, they can be combined into one message.', + example: 'Hello World', + name: 'message', + type: 'text', + }, + tags: { + category: 'base', + description: 'List of keywords used to tag each event.', + example: '["production", "env2"]', + name: 'tags', + type: 'keyword', + }, + 'agent.ephemeral_id': { + category: 'agent', + description: + 'Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but `agent.id` does not.', + example: '8a4f500f', + name: 'agent.ephemeral_id', + type: 'keyword', + }, + 'agent.id': { + category: 'agent', + description: + 'Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.', + example: '8a4f500d', + name: 'agent.id', + type: 'keyword', + }, + 'agent.name': { + category: 'agent', + description: + 'Custom name of the agent. This is a name that can be given to an agent. This can be helpful if for example two Filebeat instances are running on the same host but a human readable separation is needed on which Filebeat instance data is coming from. If no name is given, the name is often left empty.', + example: 'foo', + name: 'agent.name', + type: 'keyword', + }, + 'agent.type': { + category: 'agent', + description: + 'Type of the agent. The agent type stays always the same and should be given by the agent used. In case of Filebeat the agent would always be Filebeat also if two Filebeat instances are run on the same machine.', + example: 'filebeat', + name: 'agent.type', + type: 'keyword', + }, + 'agent.version': { + category: 'agent', + description: 'Version of the agent.', + example: '6.0.0-rc2', + name: 'agent.version', + type: 'keyword', + }, + 'as.number': { + category: 'as', + description: + 'Unique number allocated to the autonomous system. The autonomous system number (ASN) uniquely identifies each network on the Internet.', + example: 15169, + name: 'as.number', + type: 'long', + }, + 'as.organization.name': { + category: 'as', + description: 'Organization name.', + example: 'Google LLC', + name: 'as.organization.name', + type: 'keyword', + }, + 'client.address': { + category: 'client', + description: + 'Some event client addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the `.address` field. Then it should be duplicated to `.ip` or `.domain`, depending on which one it is.', + name: 'client.address', + type: 'keyword', + }, + 'client.as.number': { + category: 'client', + description: + 'Unique number allocated to the autonomous system. The autonomous system number (ASN) uniquely identifies each network on the Internet.', + example: 15169, + name: 'client.as.number', + type: 'long', + }, + 'client.as.organization.name': { + category: 'client', + description: 'Organization name.', + example: 'Google LLC', + name: 'client.as.organization.name', + type: 'keyword', + }, + 'client.bytes': { + category: 'client', + description: 'Bytes sent from the client to the server.', + example: 184, + name: 'client.bytes', + type: 'long', + format: 'bytes', + }, + 'client.domain': { + category: 'client', + description: 'Client domain.', + name: 'client.domain', + type: 'keyword', + }, + 'client.geo.city_name': { + category: 'client', + description: 'City name.', + example: 'Montreal', + name: 'client.geo.city_name', + type: 'keyword', + }, + 'client.geo.continent_name': { + category: 'client', + description: 'Name of the continent.', + example: 'North America', + name: 'client.geo.continent_name', + type: 'keyword', + }, + 'client.geo.country_iso_code': { + category: 'client', + description: 'Country ISO code.', + example: 'CA', + name: 'client.geo.country_iso_code', + type: 'keyword', + }, + 'client.geo.country_name': { + category: 'client', + description: 'Country name.', + example: 'Canada', + name: 'client.geo.country_name', + type: 'keyword', + }, + 'client.geo.location': { + category: 'client', + description: 'Longitude and latitude.', + example: '{ "lon": -73.614830, "lat": 45.505918 }', + name: 'client.geo.location', + type: 'geo_point', + }, + 'client.geo.name': { + category: 'client', + description: + 'User-defined description of a location, at the level of granularity they care about. Could be the name of their data centers, the floor number, if this describes a local physical entity, city names. Not typically used in automated geolocation.', + example: 'boston-dc', + name: 'client.geo.name', + type: 'keyword', + }, + 'client.geo.region_iso_code': { + category: 'client', + description: 'Region ISO code.', + example: 'CA-QC', + name: 'client.geo.region_iso_code', + type: 'keyword', + }, + 'client.geo.region_name': { + category: 'client', + description: 'Region name.', + example: 'Quebec', + name: 'client.geo.region_name', + type: 'keyword', + }, + 'client.ip': { + category: 'client', + description: 'IP address of the client. Can be one or multiple IPv4 or IPv6 addresses.', + name: 'client.ip', + type: 'ip', + }, + 'client.mac': { + category: 'client', + description: 'MAC address of the client.', + name: 'client.mac', + type: 'keyword', + }, + 'client.nat.ip': { + category: 'client', + description: + 'Translated IP of source based NAT sessions (e.g. internal client to internet). Typically connections traversing load balancers, firewalls, or routers.', + name: 'client.nat.ip', + type: 'ip', + }, + 'client.nat.port': { + category: 'client', + description: + 'Translated port of source based NAT sessions (e.g. internal client to internet). Typically connections traversing load balancers, firewalls, or routers.', + name: 'client.nat.port', + type: 'long', + format: 'string', + }, + 'client.packets': { + category: 'client', + description: 'Packets sent from the client to the server.', + example: 12, + name: 'client.packets', + type: 'long', + }, + 'client.port': { + category: 'client', + description: 'Port of the client.', + name: 'client.port', + type: 'long', + format: 'string', + }, + 'client.registered_domain': { + category: 'client', + description: + 'The highest registered client domain, stripped of the subdomain. For example, the registered domain for "foo.google.com" is "google.com". This value can be determined precisely with a list like the public suffix list (http://publicsuffix.org). Trying to approximate this by simply taking the last two labels will not work well for TLDs such as "co.uk".', + example: 'google.com', + name: 'client.registered_domain', + type: 'keyword', + }, + 'client.top_level_domain': { + category: 'client', + description: + 'The effective top level domain (eTLD), also known as the domain suffix, is the last part of the domain name. For example, the top level domain for google.com is "com". This value can be determined precisely with a list like the public suffix list (http://publicsuffix.org). Trying to approximate this by simply taking the last label will not work well for effective TLDs such as "co.uk".', + example: 'co.uk', + name: 'client.top_level_domain', + type: 'keyword', + }, + 'client.user.domain': { + category: 'client', + description: + 'Name of the directory the user is a member of. For example, an LDAP or Active Directory domain name.', + name: 'client.user.domain', + type: 'keyword', + }, + 'client.user.email': { + category: 'client', + description: 'User email address.', + name: 'client.user.email', + type: 'keyword', + }, + 'client.user.full_name': { + category: 'client', + description: "User's full name, if available.", + example: 'Albert Einstein', + name: 'client.user.full_name', + type: 'keyword', + }, + 'client.user.group.domain': { + category: 'client', + description: + 'Name of the directory the group is a member of. For example, an LDAP or Active Directory domain name.', + name: 'client.user.group.domain', + type: 'keyword', + }, + 'client.user.group.id': { + category: 'client', + description: 'Unique identifier for the group on the system/platform.', + name: 'client.user.group.id', + type: 'keyword', + }, + 'client.user.group.name': { + category: 'client', + description: 'Name of the group.', + name: 'client.user.group.name', + type: 'keyword', + }, + 'client.user.hash': { + category: 'client', + description: + 'Unique user hash to correlate information for a user in anonymized form. Useful if `user.id` or `user.name` contain confidential information and cannot be used.', + name: 'client.user.hash', + type: 'keyword', + }, + 'client.user.id': { + category: 'client', + description: 'Unique identifiers of the user.', + name: 'client.user.id', + type: 'keyword', + }, + 'client.user.name': { + category: 'client', + description: 'Short name or login of the user.', + example: 'albert', + name: 'client.user.name', + type: 'keyword', + }, + 'cloud.account.id': { + category: 'cloud', + description: + 'The cloud account or organization id used to identify different entities in a multi-tenant environment. Examples: AWS account id, Google Cloud ORG Id, or other unique identifier.', + example: 666777888999, + name: 'cloud.account.id', + type: 'keyword', + }, + 'cloud.availability_zone': { + category: 'cloud', + description: 'Availability zone in which this host is running.', + example: 'us-east-1c', + name: 'cloud.availability_zone', + type: 'keyword', + }, + 'cloud.instance.id': { + category: 'cloud', + description: 'Instance ID of the host machine.', + example: 'i-1234567890abcdef0', + name: 'cloud.instance.id', + type: 'keyword', + }, + 'cloud.instance.name': { + category: 'cloud', + description: 'Instance name of the host machine.', + name: 'cloud.instance.name', + type: 'keyword', + }, + 'cloud.machine.type': { + category: 'cloud', + description: 'Machine type of the host machine.', + example: 't2.medium', + name: 'cloud.machine.type', + type: 'keyword', + }, + 'cloud.provider': { + category: 'cloud', + description: 'Name of the cloud provider. Example values are aws, azure, gcp, or digitalocean.', + example: 'aws', + name: 'cloud.provider', + type: 'keyword', + }, + 'cloud.region': { + category: 'cloud', + description: 'Region in which this host is running.', + example: 'us-east-1', + name: 'cloud.region', + type: 'keyword', + }, + 'code_signature.exists': { + category: 'code_signature', + description: 'Boolean to capture if a signature is present.', + example: 'true', + name: 'code_signature.exists', + type: 'boolean', + }, + 'code_signature.status': { + category: 'code_signature', + description: + 'Additional information about the certificate status. This is useful for logging cryptographic errors with the certificate validity or trust status. Leave unpopulated if the validity or trust of the certificate was unchecked.', + example: 'ERROR_UNTRUSTED_ROOT', + name: 'code_signature.status', + type: 'keyword', + }, + 'code_signature.subject_name': { + category: 'code_signature', + description: 'Subject name of the code signer', + example: 'Microsoft Corporation', + name: 'code_signature.subject_name', + type: 'keyword', + }, + 'code_signature.trusted': { + category: 'code_signature', + description: + 'Stores the trust status of the certificate chain. Validating the trust of the certificate chain may be complicated, and this field should only be populated by tools that actively check the status.', + example: 'true', + name: 'code_signature.trusted', + type: 'boolean', + }, + 'code_signature.valid': { + category: 'code_signature', + description: + 'Boolean to capture if the digital signature is verified against the binary content. Leave unpopulated if a certificate was unchecked.', + example: 'true', + name: 'code_signature.valid', + type: 'boolean', + }, + 'container.id': { + category: 'container', + description: 'Unique container id.', + name: 'container.id', + type: 'keyword', + }, + 'container.image.name': { + category: 'container', + description: 'Name of the image the container was built on.', + name: 'container.image.name', + type: 'keyword', + }, + 'container.image.tag': { + category: 'container', + description: 'Container image tags.', + name: 'container.image.tag', + type: 'keyword', + }, + 'container.labels': { + category: 'container', + description: 'Image labels.', + name: 'container.labels', + type: 'object', + }, + 'container.name': { + category: 'container', + description: 'Container name.', + name: 'container.name', + type: 'keyword', + }, + 'container.runtime': { + category: 'container', + description: 'Runtime managing this container.', + example: 'docker', + name: 'container.runtime', + type: 'keyword', + }, + 'destination.address': { + category: 'destination', + description: + 'Some event destination addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the `.address` field. Then it should be duplicated to `.ip` or `.domain`, depending on which one it is.', + name: 'destination.address', + type: 'keyword', + }, + 'destination.as.number': { + category: 'destination', + description: + 'Unique number allocated to the autonomous system. The autonomous system number (ASN) uniquely identifies each network on the Internet.', + example: 15169, + name: 'destination.as.number', + type: 'long', + }, + 'destination.as.organization.name': { + category: 'destination', + description: 'Organization name.', + example: 'Google LLC', + name: 'destination.as.organization.name', + type: 'keyword', + }, + 'destination.bytes': { + category: 'destination', + description: 'Bytes sent from the destination to the source.', + example: 184, + name: 'destination.bytes', + type: 'long', + format: 'bytes', + }, + 'destination.domain': { + category: 'destination', + description: 'Destination domain.', + name: 'destination.domain', + type: 'keyword', + }, + 'destination.geo.city_name': { + category: 'destination', + description: 'City name.', + example: 'Montreal', + name: 'destination.geo.city_name', + type: 'keyword', + }, + 'destination.geo.continent_name': { + category: 'destination', + description: 'Name of the continent.', + example: 'North America', + name: 'destination.geo.continent_name', + type: 'keyword', + }, + 'destination.geo.country_iso_code': { + category: 'destination', + description: 'Country ISO code.', + example: 'CA', + name: 'destination.geo.country_iso_code', + type: 'keyword', + }, + 'destination.geo.country_name': { + category: 'destination', + description: 'Country name.', + example: 'Canada', + name: 'destination.geo.country_name', + type: 'keyword', + }, + 'destination.geo.location': { + category: 'destination', + description: 'Longitude and latitude.', + example: '{ "lon": -73.614830, "lat": 45.505918 }', + name: 'destination.geo.location', + type: 'geo_point', + }, + 'destination.geo.name': { + category: 'destination', + description: + 'User-defined description of a location, at the level of granularity they care about. Could be the name of their data centers, the floor number, if this describes a local physical entity, city names. Not typically used in automated geolocation.', + example: 'boston-dc', + name: 'destination.geo.name', + type: 'keyword', + }, + 'destination.geo.region_iso_code': { + category: 'destination', + description: 'Region ISO code.', + example: 'CA-QC', + name: 'destination.geo.region_iso_code', + type: 'keyword', + }, + 'destination.geo.region_name': { + category: 'destination', + description: 'Region name.', + example: 'Quebec', + name: 'destination.geo.region_name', + type: 'keyword', + }, + 'destination.ip': { + category: 'destination', + description: 'IP address of the destination. Can be one or multiple IPv4 or IPv6 addresses.', + name: 'destination.ip', + type: 'ip', + }, + 'destination.mac': { + category: 'destination', + description: 'MAC address of the destination.', + name: 'destination.mac', + type: 'keyword', + }, + 'destination.nat.ip': { + category: 'destination', + description: + 'Translated ip of destination based NAT sessions (e.g. internet to private DMZ) Typically used with load balancers, firewalls, or routers.', + name: 'destination.nat.ip', + type: 'ip', + }, + 'destination.nat.port': { + category: 'destination', + description: + 'Port the source session is translated to by NAT Device. Typically used with load balancers, firewalls, or routers.', + name: 'destination.nat.port', + type: 'long', + format: 'string', + }, + 'destination.packets': { + category: 'destination', + description: 'Packets sent from the destination to the source.', + example: 12, + name: 'destination.packets', + type: 'long', + }, + 'destination.port': { + category: 'destination', + description: 'Port of the destination.', + name: 'destination.port', + type: 'long', + format: 'string', + }, + 'destination.registered_domain': { + category: 'destination', + description: + 'The highest registered destination domain, stripped of the subdomain. For example, the registered domain for "foo.google.com" is "google.com". This value can be determined precisely with a list like the public suffix list (http://publicsuffix.org). Trying to approximate this by simply taking the last two labels will not work well for TLDs such as "co.uk".', + example: 'google.com', + name: 'destination.registered_domain', + type: 'keyword', + }, + 'destination.top_level_domain': { + category: 'destination', + description: + 'The effective top level domain (eTLD), also known as the domain suffix, is the last part of the domain name. For example, the top level domain for google.com is "com". This value can be determined precisely with a list like the public suffix list (http://publicsuffix.org). Trying to approximate this by simply taking the last label will not work well for effective TLDs such as "co.uk".', + example: 'co.uk', + name: 'destination.top_level_domain', + type: 'keyword', + }, + 'destination.user.domain': { + category: 'destination', + description: + 'Name of the directory the user is a member of. For example, an LDAP or Active Directory domain name.', + name: 'destination.user.domain', + type: 'keyword', + }, + 'destination.user.email': { + category: 'destination', + description: 'User email address.', + name: 'destination.user.email', + type: 'keyword', + }, + 'destination.user.full_name': { + category: 'destination', + description: "User's full name, if available.", + example: 'Albert Einstein', + name: 'destination.user.full_name', + type: 'keyword', + }, + 'destination.user.group.domain': { + category: 'destination', + description: + 'Name of the directory the group is a member of. For example, an LDAP or Active Directory domain name.', + name: 'destination.user.group.domain', + type: 'keyword', + }, + 'destination.user.group.id': { + category: 'destination', + description: 'Unique identifier for the group on the system/platform.', + name: 'destination.user.group.id', + type: 'keyword', + }, + 'destination.user.group.name': { + category: 'destination', + description: 'Name of the group.', + name: 'destination.user.group.name', + type: 'keyword', + }, + 'destination.user.hash': { + category: 'destination', + description: + 'Unique user hash to correlate information for a user in anonymized form. Useful if `user.id` or `user.name` contain confidential information and cannot be used.', + name: 'destination.user.hash', + type: 'keyword', + }, + 'destination.user.id': { + category: 'destination', + description: 'Unique identifiers of the user.', + name: 'destination.user.id', + type: 'keyword', + }, + 'destination.user.name': { + category: 'destination', + description: 'Short name or login of the user.', + example: 'albert', + name: 'destination.user.name', + type: 'keyword', + }, + 'dll.code_signature.exists': { + category: 'dll', + description: 'Boolean to capture if a signature is present.', + example: 'true', + name: 'dll.code_signature.exists', + type: 'boolean', + }, + 'dll.code_signature.status': { + category: 'dll', + description: + 'Additional information about the certificate status. This is useful for logging cryptographic errors with the certificate validity or trust status. Leave unpopulated if the validity or trust of the certificate was unchecked.', + example: 'ERROR_UNTRUSTED_ROOT', + name: 'dll.code_signature.status', + type: 'keyword', + }, + 'dll.code_signature.subject_name': { + category: 'dll', + description: 'Subject name of the code signer', + example: 'Microsoft Corporation', + name: 'dll.code_signature.subject_name', + type: 'keyword', + }, + 'dll.code_signature.trusted': { + category: 'dll', + description: + 'Stores the trust status of the certificate chain. Validating the trust of the certificate chain may be complicated, and this field should only be populated by tools that actively check the status.', + example: 'true', + name: 'dll.code_signature.trusted', + type: 'boolean', + }, + 'dll.code_signature.valid': { + category: 'dll', + description: + 'Boolean to capture if the digital signature is verified against the binary content. Leave unpopulated if a certificate was unchecked.', + example: 'true', + name: 'dll.code_signature.valid', + type: 'boolean', + }, + 'dll.hash.md5': { + category: 'dll', + description: 'MD5 hash.', + name: 'dll.hash.md5', + type: 'keyword', + }, + 'dll.hash.sha1': { + category: 'dll', + description: 'SHA1 hash.', + name: 'dll.hash.sha1', + type: 'keyword', + }, + 'dll.hash.sha256': { + category: 'dll', + description: 'SHA256 hash.', + name: 'dll.hash.sha256', + type: 'keyword', + }, + 'dll.hash.sha512': { + category: 'dll', + description: 'SHA512 hash.', + name: 'dll.hash.sha512', + type: 'keyword', + }, + 'dll.name': { + category: 'dll', + description: 'Name of the library. This generally maps to the name of the file on disk.', + example: 'kernel32.dll', + name: 'dll.name', + type: 'keyword', + }, + 'dll.path': { + category: 'dll', + description: 'Full file path of the library.', + example: 'C:\\Windows\\System32\\kernel32.dll', + name: 'dll.path', + type: 'keyword', + }, + 'dll.pe.company': { + category: 'dll', + description: 'Internal company name of the file, provided at compile-time.', + example: 'Microsoft Corporation', + name: 'dll.pe.company', + type: 'keyword', + }, + 'dll.pe.description': { + category: 'dll', + description: 'Internal description of the file, provided at compile-time.', + example: 'Paint', + name: 'dll.pe.description', + type: 'keyword', + }, + 'dll.pe.file_version': { + category: 'dll', + description: 'Internal version of the file, provided at compile-time.', + example: '6.3.9600.17415', + name: 'dll.pe.file_version', + type: 'keyword', + }, + 'dll.pe.original_file_name': { + category: 'dll', + description: 'Internal name of the file, provided at compile-time.', + example: 'MSPAINT.EXE', + name: 'dll.pe.original_file_name', + type: 'keyword', + }, + 'dll.pe.product': { + category: 'dll', + description: 'Internal product name of the file, provided at compile-time.', + example: 'Microsoft® Windows® Operating System', + name: 'dll.pe.product', + type: 'keyword', + }, + 'dns.answers': { + category: 'dns', + description: + 'An array containing an object for each answer section returned by the server. The main keys that should be present in these objects are defined by ECS. Records that have more information may contain more keys than what ECS defines. Not all DNS data sources give all details about DNS answers. At minimum, answer objects must contain the `data` key. If more information is available, map as much of it to ECS as possible, and add any additional fields to the answer objects as custom fields.', + name: 'dns.answers', + type: 'object', + }, + 'dns.answers.class': { + category: 'dns', + description: 'The class of DNS data contained in this resource record.', + example: 'IN', + name: 'dns.answers.class', + type: 'keyword', + }, + 'dns.answers.data': { + category: 'dns', + description: + 'The data describing the resource. The meaning of this data depends on the type and class of the resource record.', + example: '10.10.10.10', + name: 'dns.answers.data', + type: 'keyword', + }, + 'dns.answers.name': { + category: 'dns', + description: + "The domain name to which this resource record pertains. If a chain of CNAME is being resolved, each answer's `name` should be the one that corresponds with the answer's `data`. It should not simply be the original `question.name` repeated.", + example: 'www.google.com', + name: 'dns.answers.name', + type: 'keyword', + }, + 'dns.answers.ttl': { + category: 'dns', + description: + 'The time interval in seconds that this resource record may be cached before it should be discarded. Zero values mean that the data should not be cached.', + example: 180, + name: 'dns.answers.ttl', + type: 'long', + }, + 'dns.answers.type': { + category: 'dns', + description: 'The type of data contained in this resource record.', + example: 'CNAME', + name: 'dns.answers.type', + type: 'keyword', + }, + 'dns.header_flags': { + category: 'dns', + description: + 'Array of 2 letter DNS header flags. Expected values are: AA, TC, RD, RA, AD, CD, DO.', + example: '["RD","RA"]', + name: 'dns.header_flags', + type: 'keyword', + }, + 'dns.id': { + category: 'dns', + description: + 'The DNS packet identifier assigned by the program that generated the query. The identifier is copied to the response.', + example: 62111, + name: 'dns.id', + type: 'keyword', + }, + 'dns.op_code': { + category: 'dns', + description: + 'The DNS operation code that specifies the kind of query in the message. This value is set by the originator of a query and copied into the response.', + example: 'QUERY', + name: 'dns.op_code', + type: 'keyword', + }, + 'dns.question.class': { + category: 'dns', + description: 'The class of records being queried.', + example: 'IN', + name: 'dns.question.class', + type: 'keyword', + }, + 'dns.question.name': { + category: 'dns', + description: + 'The name being queried. If the name field contains non-printable characters (below 32 or above 126), those characters should be represented as escaped base 10 integers (\\DDD). Back slashes and quotes should be escaped. Tabs, carriage returns, and line feeds should be converted to \\t, \\r, and \\n respectively.', + example: 'www.google.com', + name: 'dns.question.name', + type: 'keyword', + }, + 'dns.question.registered_domain': { + category: 'dns', + description: + 'The highest registered domain, stripped of the subdomain. For example, the registered domain for "foo.google.com" is "google.com". This value can be determined precisely with a list like the public suffix list (http://publicsuffix.org). Trying to approximate this by simply taking the last two labels will not work well for TLDs such as "co.uk".', + example: 'google.com', + name: 'dns.question.registered_domain', + type: 'keyword', + }, + 'dns.question.subdomain': { + category: 'dns', + description: + 'The subdomain is all of the labels under the registered_domain. If the domain has multiple levels of subdomain, such as "sub2.sub1.example.com", the subdomain field should contain "sub2.sub1", with no trailing period.', + example: 'www', + name: 'dns.question.subdomain', + type: 'keyword', + }, + 'dns.question.top_level_domain': { + category: 'dns', + description: + 'The effective top level domain (eTLD), also known as the domain suffix, is the last part of the domain name. For example, the top level domain for google.com is "com". This value can be determined precisely with a list like the public suffix list (http://publicsuffix.org). Trying to approximate this by simply taking the last label will not work well for effective TLDs such as "co.uk".', + example: 'co.uk', + name: 'dns.question.top_level_domain', + type: 'keyword', + }, + 'dns.question.type': { + category: 'dns', + description: 'The type of record being queried.', + example: 'AAAA', + name: 'dns.question.type', + type: 'keyword', + }, + 'dns.resolved_ip': { + category: 'dns', + description: + 'Array containing all IPs seen in `answers.data`. The `answers` array can be difficult to use, because of the variety of data formats it can contain. Extracting all IP addresses seen in there to `dns.resolved_ip` makes it possible to index them as IP addresses, and makes them easier to visualize and query for.', + example: '["10.10.10.10","10.10.10.11"]', + name: 'dns.resolved_ip', + type: 'ip', + }, + 'dns.response_code': { + category: 'dns', + description: 'The DNS response code.', + example: 'NOERROR', + name: 'dns.response_code', + type: 'keyword', + }, + 'dns.type': { + category: 'dns', + description: + 'The type of DNS event captured, query or answer. If your source of DNS events only gives you DNS queries, you should only create dns events of type `dns.type:query`. If your source of DNS events gives you answers as well, you should create one event per query (optionally as soon as the query is seen). And a second event containing all query details as well as an array of answers.', + example: 'answer', + name: 'dns.type', + type: 'keyword', + }, + 'ecs.version': { + category: 'ecs', + description: + 'ECS version this event conforms to. `ecs.version` is a required field and must exist in all events. When querying across multiple indices -- which may conform to slightly different ECS versions -- this field lets integrations adjust to the schema version of the events.', + example: '1.0.0', + name: 'ecs.version', + type: 'keyword', + }, + 'error.code': { + category: 'error', + description: 'Error code describing the error.', + name: 'error.code', + type: 'keyword', + }, + 'error.id': { + category: 'error', + description: 'Unique identifier for the error.', + name: 'error.id', + type: 'keyword', + }, + 'error.message': { + category: 'error', + description: 'Error message.', + name: 'error.message', + type: 'text', + }, + 'error.stack_trace': { + category: 'error', + description: 'The stack trace of this error in plain text.', + name: 'error.stack_trace', + type: 'keyword', + }, + 'error.type': { + category: 'error', + description: 'The type of the error, for example the class name of the exception.', + example: 'java.lang.NullPointerException', + name: 'error.type', + type: 'keyword', + }, + 'event.action': { + category: 'event', + description: + 'The action captured by the event. This describes the information in the event. It is more specific than `event.category`. Examples are `group-add`, `process-started`, `file-created`. The value is normally defined by the implementer.', + example: 'user-password-change', + name: 'event.action', + type: 'keyword', + }, + 'event.category': { + category: 'event', + description: + 'This is one of four ECS Categorization Fields, and indicates the second level in the ECS category hierarchy. `event.category` represents the "big buckets" of ECS categories. For example, filtering on `event.category:process` yields all events relating to process activity. This field is closely related to `event.type`, which is used as a subcategory. This field is an array. This will allow proper categorization of some events that fall in multiple categories.', + example: 'authentication', + name: 'event.category', + type: 'keyword', + }, + 'event.code': { + category: 'event', + description: + 'Identification code for this event, if one exists. Some event sources use event codes to identify messages unambiguously, regardless of message language or wording adjustments over time. An example of this is the Windows Event ID.', + example: 4648, + name: 'event.code', + type: 'keyword', + }, + 'event.created': { + category: 'event', + description: + "event.created contains the date/time when the event was first read by an agent, or by your pipeline. This field is distinct from @timestamp in that @timestamp typically contain the time extracted from the original event. In most situations, these two timestamps will be slightly different. The difference can be used to calculate the delay between your source generating an event, and the time when your agent first processed it. This can be used to monitor your agent's or pipeline's ability to keep up with your event source. In case the two timestamps are identical, @timestamp should be used.", + example: '2016-05-23T08:05:34.857Z', + name: 'event.created', + type: 'date', + }, + 'event.dataset': { + category: 'event', + description: + "Name of the dataset. If an event source publishes more than one type of log or events (e.g. access log, error log), the dataset is used to specify which one the event comes from. It's recommended but not required to start the dataset name with the module name, followed by a dot, then the dataset name.", + example: 'apache.access', + name: 'event.dataset', + type: 'keyword', + }, + 'event.duration': { + category: 'event', + description: + 'Duration of the event in nanoseconds. If event.start and event.end are known this value should be the difference between the end and start time.', + name: 'event.duration', + type: 'long', + format: 'duration', + }, + 'event.end': { + category: 'event', + description: + 'event.end contains the date when the event ended or when the activity was last observed.', + name: 'event.end', + type: 'date', + }, + 'event.hash': { + category: 'event', + description: + 'Hash (perhaps logstash fingerprint) of raw field to be able to demonstrate log integrity.', + example: '123456789012345678901234567890ABCD', + name: 'event.hash', + type: 'keyword', + }, + 'event.id': { + category: 'event', + description: 'Unique ID to describe the event.', + example: '8a4f500d', + name: 'event.id', + type: 'keyword', + }, + 'event.ingested': { + category: 'event', + description: + "Timestamp when an event arrived in the central data store. This is different from `@timestamp`, which is when the event originally occurred. It's also different from `event.created`, which is meant to capture the first time an agent saw the event. In normal conditions, assuming no tampering, the timestamps should chronologically look like this: `@timestamp` < `event.created` < `event.ingested`.", + example: '2016-05-23T08:05:35.101Z', + name: 'event.ingested', + type: 'date', + }, + 'event.kind': { + category: 'event', + description: + 'This is one of four ECS Categorization Fields, and indicates the highest level in the ECS category hierarchy. `event.kind` gives high-level information about what type of information the event contains, without being specific to the contents of the event. For example, values of this field distinguish alert events from metric events. The value of this field can be used to inform how these kinds of events should be handled. They may warrant different retention, different access control, it may also help understand whether the data coming in at a regular interval or not.', + example: 'alert', + name: 'event.kind', + type: 'keyword', + }, + 'event.module': { + category: 'event', + description: + 'Name of the module this data is coming from. If your monitoring agent supports the concept of modules or plugins to process events of a given source (e.g. Apache logs), `event.module` should contain the name of this module.', + example: 'apache', + name: 'event.module', + type: 'keyword', + }, + 'event.original': { + category: 'event', + description: + 'Raw text message of entire event. Used to demonstrate log integrity. This field is not indexed and doc_values are disabled. It cannot be searched, but it can be retrieved from `_source`.', + example: + 'Sep 19 08:26:10 host CEF:0|Security| threatmanager|1.0|100| worm successfully stopped|10|src=10.0.0.1 dst=2.1.2.2spt=1232', + name: 'event.original', + type: 'keyword', + }, + 'event.outcome': { + category: 'event', + description: + 'This is one of four ECS Categorization Fields, and indicates the lowest level in the ECS category hierarchy. `event.outcome` simply denotes whether the event represents a success or a failure from the perspective of the entity that produced the event. Note that when a single transaction is described in multiple events, each event may populate different values of `event.outcome`, according to their perspective. Also note that in the case of a compound event (a single event that contains multiple logical events), this field should be populated with the value that best captures the overall success or failure from the perspective of the event producer. Further note that not all events will have an associated outcome. For example, this field is generally not populated for metric events, events with `event.type:info`, or any events for which an outcome does not make logical sense.', + example: 'success', + name: 'event.outcome', + type: 'keyword', + }, + 'event.provider': { + category: 'event', + description: + 'Source of the event. Event transports such as Syslog or the Windows Event Log typically mention the source of an event. It can be the name of the software that generated the event (e.g. Sysmon, httpd), or of a subsystem of the operating system (kernel, Microsoft-Windows-Security-Auditing).', + example: 'kernel', + name: 'event.provider', + type: 'keyword', + }, + 'event.reference': { + category: 'event', + description: + 'Reference URL linking to additional information about this event. This URL links to a static definition of the this event. Alert events, indicated by `event.kind:alert`, are a common use case for this field.', + example: 'https://system.vendor.com/event/#0001234', + name: 'event.reference', + type: 'keyword', + }, + 'event.risk_score': { + category: 'event', + description: + "Risk score or priority of the event (e.g. security solutions). Use your system's original value here.", + name: 'event.risk_score', + type: 'float', + }, + 'event.risk_score_norm': { + category: 'event', + description: + 'Normalized risk score or priority of the event, on a scale of 0 to 100. This is mainly useful if you use more than one system that assigns risk scores, and you want to see a normalized value across all systems.', + name: 'event.risk_score_norm', + type: 'float', + }, + 'event.sequence': { + category: 'event', + description: + 'Sequence number of the event. The sequence number is a value published by some event sources, to make the exact ordering of events unambiguous, regardless of the timestamp precision.', + name: 'event.sequence', + type: 'long', + format: 'string', + }, + 'event.severity': { + category: 'event', + description: + "The numeric severity of the event according to your event source. What the different severity values mean can be different between sources and use cases. It's up to the implementer to make sure severities are consistent across events from the same source. The Syslog severity belongs in `log.syslog.severity.code`. `event.severity` is meant to represent the severity according to the event source (e.g. firewall, IDS). If the event source does not publish its own severity, you may optionally copy the `log.syslog.severity.code` to `event.severity`.", + example: 7, + name: 'event.severity', + type: 'long', + format: 'string', + }, + 'event.start': { + category: 'event', + description: + 'event.start contains the date when the event started or when the activity was first observed.', + name: 'event.start', + type: 'date', + }, + 'event.timezone': { + category: 'event', + description: + 'This field should be populated when the event\'s timestamp does not include timezone information already (e.g. default Syslog timestamps). It\'s optional otherwise. Acceptable timezone formats are: a canonical ID (e.g. "Europe/Amsterdam"), abbreviated (e.g. "EST") or an HH:mm differential (e.g. "-05:00").', + name: 'event.timezone', + type: 'keyword', + }, + 'event.type': { + category: 'event', + description: + 'This is one of four ECS Categorization Fields, and indicates the third level in the ECS category hierarchy. `event.type` represents a categorization "sub-bucket" that, when used along with the `event.category` field values, enables filtering events down to a level appropriate for single visualization. This field is an array. This will allow proper categorization of some events that fall in multiple event types.', + name: 'event.type', + type: 'keyword', + }, + 'event.url': { + category: 'event', + description: + 'URL linking to an external system to continue investigation of this event. This URL links to another system where in-depth investigation of the specific occurence of this event can take place. Alert events, indicated by `event.kind:alert`, are a common use case for this field.', + example: 'https://mysystem.mydomain.com/alert/5271dedb-f5b0-4218-87f0-4ac4870a38fe', + name: 'event.url', + type: 'keyword', + }, + 'file.accessed': { + category: 'file', + description: + 'Last time the file was accessed. Note that not all filesystems keep track of access time.', + name: 'file.accessed', + type: 'date', + }, + 'file.attributes': { + category: 'file', + description: + "Array of file attributes. Attributes names will vary by platform. Here's a non-exhaustive list of values that are expected in this field: archive, compressed, directory, encrypted, execute, hidden, read, readonly, system, write.", + example: '["readonly", "system"]', + name: 'file.attributes', + type: 'keyword', + }, + 'file.code_signature.exists': { + category: 'file', + description: 'Boolean to capture if a signature is present.', + example: 'true', + name: 'file.code_signature.exists', + type: 'boolean', + }, + 'file.code_signature.status': { + category: 'file', + description: + 'Additional information about the certificate status. This is useful for logging cryptographic errors with the certificate validity or trust status. Leave unpopulated if the validity or trust of the certificate was unchecked.', + example: 'ERROR_UNTRUSTED_ROOT', + name: 'file.code_signature.status', + type: 'keyword', + }, + 'file.code_signature.subject_name': { + category: 'file', + description: 'Subject name of the code signer', + example: 'Microsoft Corporation', + name: 'file.code_signature.subject_name', + type: 'keyword', + }, + 'file.code_signature.trusted': { + category: 'file', + description: + 'Stores the trust status of the certificate chain. Validating the trust of the certificate chain may be complicated, and this field should only be populated by tools that actively check the status.', + example: 'true', + name: 'file.code_signature.trusted', + type: 'boolean', + }, + 'file.code_signature.valid': { + category: 'file', + description: + 'Boolean to capture if the digital signature is verified against the binary content. Leave unpopulated if a certificate was unchecked.', + example: 'true', + name: 'file.code_signature.valid', + type: 'boolean', + }, + 'file.created': { + category: 'file', + description: 'File creation time. Note that not all filesystems store the creation time.', + name: 'file.created', + type: 'date', + }, + 'file.ctime': { + category: 'file', + description: + 'Last time the file attributes or metadata changed. Note that changes to the file content will update `mtime`. This implies `ctime` will be adjusted at the same time, since `mtime` is an attribute of the file.', + name: 'file.ctime', + type: 'date', + }, + 'file.device': { + category: 'file', + description: 'Device that is the source of the file.', + example: 'sda', + name: 'file.device', + type: 'keyword', + }, + 'file.directory': { + category: 'file', + description: + 'Directory where the file is located. It should include the drive letter, when appropriate.', + example: '/home/alice', + name: 'file.directory', + type: 'keyword', + }, + 'file.drive_letter': { + category: 'file', + description: + 'Drive letter where the file is located. This field is only relevant on Windows. The value should be uppercase, and not include the colon.', + example: 'C', + name: 'file.drive_letter', + type: 'keyword', + }, + 'file.extension': { + category: 'file', + description: 'File extension.', + example: 'png', + name: 'file.extension', + type: 'keyword', + }, + 'file.gid': { + category: 'file', + description: 'Primary group ID (GID) of the file.', + example: '1001', + name: 'file.gid', + type: 'keyword', + }, + 'file.group': { + category: 'file', + description: 'Primary group name of the file.', + example: 'alice', + name: 'file.group', + type: 'keyword', + }, + 'file.hash.md5': { + category: 'file', + description: 'MD5 hash.', + name: 'file.hash.md5', + type: 'keyword', + }, + 'file.hash.sha1': { + category: 'file', + description: 'SHA1 hash.', + name: 'file.hash.sha1', + type: 'keyword', + }, + 'file.hash.sha256': { + category: 'file', + description: 'SHA256 hash.', + name: 'file.hash.sha256', + type: 'keyword', + }, + 'file.hash.sha512': { + category: 'file', + description: 'SHA512 hash.', + name: 'file.hash.sha512', + type: 'keyword', + }, + 'file.inode': { + category: 'file', + description: 'Inode representing the file in the filesystem.', + example: '256383', + name: 'file.inode', + type: 'keyword', + }, + 'file.mime_type': { + category: 'file', + description: + 'MIME type should identify the format of the file or stream of bytes using https://www.iana.org/assignments/media-types/media-types.xhtml[IANA official types], where possible. When more than one type is applicable, the most specific type should be used.', + name: 'file.mime_type', + type: 'keyword', + }, + 'file.mode': { + category: 'file', + description: 'Mode of the file in octal representation.', + example: '0640', + name: 'file.mode', + type: 'keyword', + }, + 'file.mtime': { + category: 'file', + description: 'Last time the file content was modified.', + name: 'file.mtime', + type: 'date', + }, + 'file.name': { + category: 'file', + description: 'Name of the file including the extension, without the directory.', + example: 'example.png', + name: 'file.name', + type: 'keyword', + }, + 'file.owner': { + category: 'file', + description: "File owner's username.", + example: 'alice', + name: 'file.owner', + type: 'keyword', + }, + 'file.path': { + category: 'file', + description: + 'Full path to the file, including the file name. It should include the drive letter, when appropriate.', + example: '/home/alice/example.png', + name: 'file.path', + type: 'keyword', + }, + 'file.pe.company': { + category: 'file', + description: 'Internal company name of the file, provided at compile-time.', + example: 'Microsoft Corporation', + name: 'file.pe.company', + type: 'keyword', + }, + 'file.pe.description': { + category: 'file', + description: 'Internal description of the file, provided at compile-time.', + example: 'Paint', + name: 'file.pe.description', + type: 'keyword', + }, + 'file.pe.file_version': { + category: 'file', + description: 'Internal version of the file, provided at compile-time.', + example: '6.3.9600.17415', + name: 'file.pe.file_version', + type: 'keyword', + }, + 'file.pe.original_file_name': { + category: 'file', + description: 'Internal name of the file, provided at compile-time.', + example: 'MSPAINT.EXE', + name: 'file.pe.original_file_name', + type: 'keyword', + }, + 'file.pe.product': { + category: 'file', + description: 'Internal product name of the file, provided at compile-time.', + example: 'Microsoft® Windows® Operating System', + name: 'file.pe.product', + type: 'keyword', + }, + 'file.size': { + category: 'file', + description: 'File size in bytes. Only relevant when `file.type` is "file".', + example: 16384, + name: 'file.size', + type: 'long', + }, + 'file.target_path': { + category: 'file', + description: 'Target path for symlinks.', + name: 'file.target_path', + type: 'keyword', + }, + 'file.type': { + category: 'file', + description: 'File type (file, dir, or symlink).', + example: 'file', + name: 'file.type', + type: 'keyword', + }, + 'file.uid': { + category: 'file', + description: 'The user ID (UID) or security identifier (SID) of the file owner.', + example: '1001', + name: 'file.uid', + type: 'keyword', + }, + 'geo.city_name': { + category: 'geo', + description: 'City name.', + example: 'Montreal', + name: 'geo.city_name', + type: 'keyword', + }, + 'geo.continent_name': { + category: 'geo', + description: 'Name of the continent.', + example: 'North America', + name: 'geo.continent_name', + type: 'keyword', + }, + 'geo.country_iso_code': { + category: 'geo', + description: 'Country ISO code.', + example: 'CA', + name: 'geo.country_iso_code', + type: 'keyword', + }, + 'geo.country_name': { + category: 'geo', + description: 'Country name.', + example: 'Canada', + name: 'geo.country_name', + type: 'keyword', + }, + 'geo.location': { + category: 'geo', + description: 'Longitude and latitude.', + example: '{ "lon": -73.614830, "lat": 45.505918 }', + name: 'geo.location', + type: 'geo_point', + }, + 'geo.name': { + category: 'geo', + description: + 'User-defined description of a location, at the level of granularity they care about. Could be the name of their data centers, the floor number, if this describes a local physical entity, city names. Not typically used in automated geolocation.', + example: 'boston-dc', + name: 'geo.name', + type: 'keyword', + }, + 'geo.region_iso_code': { + category: 'geo', + description: 'Region ISO code.', + example: 'CA-QC', + name: 'geo.region_iso_code', + type: 'keyword', + }, + 'geo.region_name': { + category: 'geo', + description: 'Region name.', + example: 'Quebec', + name: 'geo.region_name', + type: 'keyword', + }, + 'group.domain': { + category: 'group', + description: + 'Name of the directory the group is a member of. For example, an LDAP or Active Directory domain name.', + name: 'group.domain', + type: 'keyword', + }, + 'group.id': { + category: 'group', + description: 'Unique identifier for the group on the system/platform.', + name: 'group.id', + type: 'keyword', + }, + 'group.name': { + category: 'group', + description: 'Name of the group.', + name: 'group.name', + type: 'keyword', + }, + 'hash.md5': { + category: 'hash', + description: 'MD5 hash.', + name: 'hash.md5', + type: 'keyword', + }, + 'hash.sha1': { + category: 'hash', + description: 'SHA1 hash.', + name: 'hash.sha1', + type: 'keyword', + }, + 'hash.sha256': { + category: 'hash', + description: 'SHA256 hash.', + name: 'hash.sha256', + type: 'keyword', + }, + 'hash.sha512': { + category: 'hash', + description: 'SHA512 hash.', + name: 'hash.sha512', + type: 'keyword', + }, + 'host.architecture': { + category: 'host', + description: 'Operating system architecture.', + example: 'x86_64', + name: 'host.architecture', + type: 'keyword', + }, + 'host.domain': { + category: 'host', + description: + "Name of the domain of which the host is a member. For example, on Windows this could be the host's Active Directory domain or NetBIOS domain name. For Linux this could be the domain of the host's LDAP provider.", + example: 'CONTOSO', + name: 'host.domain', + type: 'keyword', + }, + 'host.geo.city_name': { + category: 'host', + description: 'City name.', + example: 'Montreal', + name: 'host.geo.city_name', + type: 'keyword', + }, + 'host.geo.continent_name': { + category: 'host', + description: 'Name of the continent.', + example: 'North America', + name: 'host.geo.continent_name', + type: 'keyword', + }, + 'host.geo.country_iso_code': { + category: 'host', + description: 'Country ISO code.', + example: 'CA', + name: 'host.geo.country_iso_code', + type: 'keyword', + }, + 'host.geo.country_name': { + category: 'host', + description: 'Country name.', + example: 'Canada', + name: 'host.geo.country_name', + type: 'keyword', + }, + 'host.geo.location': { + category: 'host', + description: 'Longitude and latitude.', + example: '{ "lon": -73.614830, "lat": 45.505918 }', + name: 'host.geo.location', + type: 'geo_point', + }, + 'host.geo.name': { + category: 'host', + description: + 'User-defined description of a location, at the level of granularity they care about. Could be the name of their data centers, the floor number, if this describes a local physical entity, city names. Not typically used in automated geolocation.', + example: 'boston-dc', + name: 'host.geo.name', + type: 'keyword', + }, + 'host.geo.region_iso_code': { + category: 'host', + description: 'Region ISO code.', + example: 'CA-QC', + name: 'host.geo.region_iso_code', + type: 'keyword', + }, + 'host.geo.region_name': { + category: 'host', + description: 'Region name.', + example: 'Quebec', + name: 'host.geo.region_name', + type: 'keyword', + }, + 'host.hostname': { + category: 'host', + description: + 'Hostname of the host. It normally contains what the `hostname` command returns on the host machine.', + name: 'host.hostname', + type: 'keyword', + }, + 'host.id': { + category: 'host', + description: + 'Unique host id. As hostname is not always unique, use values that are meaningful in your environment. Example: The current usage of `beat.name`.', + name: 'host.id', + type: 'keyword', + }, + 'host.ip': { + category: 'host', + description: 'Host ip addresses.', + name: 'host.ip', + type: 'ip', + }, + 'host.mac': { + category: 'host', + description: 'Host mac addresses.', + name: 'host.mac', + type: 'keyword', + }, + 'host.name': { + category: 'host', + description: + 'Name of the host. It can contain what `hostname` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.', + name: 'host.name', + type: 'keyword', + }, + 'host.os.family': { + category: 'host', + description: 'OS family (such as redhat, debian, freebsd, windows).', + example: 'debian', + name: 'host.os.family', + type: 'keyword', + }, + 'host.os.full': { + category: 'host', + description: 'Operating system name, including the version or code name.', + example: 'Mac OS Mojave', + name: 'host.os.full', + type: 'keyword', + }, + 'host.os.kernel': { + category: 'host', + description: 'Operating system kernel version as a raw string.', + example: '4.4.0-112-generic', + name: 'host.os.kernel', + type: 'keyword', + }, + 'host.os.name': { + category: 'host', + description: 'Operating system name, without the version.', + example: 'Mac OS X', + name: 'host.os.name', + type: 'keyword', + }, + 'host.os.platform': { + category: 'host', + description: 'Operating system platform (such centos, ubuntu, windows).', + example: 'darwin', + name: 'host.os.platform', + type: 'keyword', + }, + 'host.os.version': { + category: 'host', + description: 'Operating system version as a raw string.', + example: '10.14.1', + name: 'host.os.version', + type: 'keyword', + }, + 'host.type': { + category: 'host', + description: + 'Type of host. For Cloud providers this can be the machine type like `t2.medium`. If vm, this could be the container, for example, or other information meaningful in your environment.', + name: 'host.type', + type: 'keyword', + }, + 'host.uptime': { + category: 'host', + description: 'Seconds the host has been up.', + example: 1325, + name: 'host.uptime', + type: 'long', + }, + 'host.user.domain': { + category: 'host', + description: + 'Name of the directory the user is a member of. For example, an LDAP or Active Directory domain name.', + name: 'host.user.domain', + type: 'keyword', + }, + 'host.user.email': { + category: 'host', + description: 'User email address.', + name: 'host.user.email', + type: 'keyword', + }, + 'host.user.full_name': { + category: 'host', + description: "User's full name, if available.", + example: 'Albert Einstein', + name: 'host.user.full_name', + type: 'keyword', + }, + 'host.user.group.domain': { + category: 'host', + description: + 'Name of the directory the group is a member of. For example, an LDAP or Active Directory domain name.', + name: 'host.user.group.domain', + type: 'keyword', + }, + 'host.user.group.id': { + category: 'host', + description: 'Unique identifier for the group on the system/platform.', + name: 'host.user.group.id', + type: 'keyword', + }, + 'host.user.group.name': { + category: 'host', + description: 'Name of the group.', + name: 'host.user.group.name', + type: 'keyword', + }, + 'host.user.hash': { + category: 'host', + description: + 'Unique user hash to correlate information for a user in anonymized form. Useful if `user.id` or `user.name` contain confidential information and cannot be used.', + name: 'host.user.hash', + type: 'keyword', + }, + 'host.user.id': { + category: 'host', + description: 'Unique identifiers of the user.', + name: 'host.user.id', + type: 'keyword', + }, + 'host.user.name': { + category: 'host', + description: 'Short name or login of the user.', + example: 'albert', + name: 'host.user.name', + type: 'keyword', + }, + 'http.request.body.bytes': { + category: 'http', + description: 'Size in bytes of the request body.', + example: 887, + name: 'http.request.body.bytes', + type: 'long', + format: 'bytes', + }, + 'http.request.body.content': { + category: 'http', + description: 'The full HTTP request body.', + example: 'Hello world', + name: 'http.request.body.content', + type: 'keyword', + }, + 'http.request.bytes': { + category: 'http', + description: 'Total size in bytes of the request (body and headers).', + example: 1437, + name: 'http.request.bytes', + type: 'long', + format: 'bytes', + }, + 'http.request.method': { + category: 'http', + description: + 'HTTP request method. The field value must be normalized to lowercase for querying. See the documentation section "Implementing ECS".', + example: 'get, post, put', + name: 'http.request.method', + type: 'keyword', + }, + 'http.request.referrer': { + category: 'http', + description: 'Referrer for this HTTP request.', + example: 'https://blog.example.com/', + name: 'http.request.referrer', + type: 'keyword', + }, + 'http.response.body.bytes': { + category: 'http', + description: 'Size in bytes of the response body.', + example: 887, + name: 'http.response.body.bytes', + type: 'long', + format: 'bytes', + }, + 'http.response.body.content': { + category: 'http', + description: 'The full HTTP response body.', + example: 'Hello world', + name: 'http.response.body.content', + type: 'keyword', + }, + 'http.response.bytes': { + category: 'http', + description: 'Total size in bytes of the response (body and headers).', + example: 1437, + name: 'http.response.bytes', + type: 'long', + format: 'bytes', + }, + 'http.response.status_code': { + category: 'http', + description: 'HTTP response status code.', + example: 404, + name: 'http.response.status_code', + type: 'long', + format: 'string', + }, + 'http.version': { + category: 'http', + description: 'HTTP version.', + example: 1.1, + name: 'http.version', + type: 'keyword', + }, + 'interface.alias': { + category: 'interface', + description: + 'Interface alias as reported by the system, typically used in firewall implementations for e.g. inside, outside, or dmz logical interface naming.', + example: 'outside', + name: 'interface.alias', + type: 'keyword', + }, + 'interface.id': { + category: 'interface', + description: 'Interface ID as reported by an observer (typically SNMP interface ID).', + example: 10, + name: 'interface.id', + type: 'keyword', + }, + 'interface.name': { + category: 'interface', + description: 'Interface name as reported by the system.', + example: 'eth0', + name: 'interface.name', + type: 'keyword', + }, + 'log.level': { + category: 'log', + description: + "Original log level of the log event. If the source of the event provides a log level or textual severity, this is the one that goes in `log.level`. If your source doesn't specify one, you may put your event transport's severity here (e.g. Syslog severity). Some examples are `warn`, `err`, `i`, `informational`.", + example: 'error', + name: 'log.level', + type: 'keyword', + }, + 'log.logger': { + category: 'log', + description: + 'The name of the logger inside an application. This is usually the name of the class which initialized the logger, or can be a custom name.', + example: 'org.elasticsearch.bootstrap.Bootstrap', + name: 'log.logger', + type: 'keyword', + }, + 'log.origin.file.line': { + category: 'log', + description: + 'The line number of the file containing the source code which originated the log event.', + example: 42, + name: 'log.origin.file.line', + type: 'integer', + }, + 'log.origin.file.name': { + category: 'log', + description: + 'The name of the file containing the source code which originated the log event. Note that this is not the name of the log file.', + example: 'Bootstrap.java', + name: 'log.origin.file.name', + type: 'keyword', + }, + 'log.origin.function': { + category: 'log', + description: 'The name of the function or method which originated the log event.', + example: 'init', + name: 'log.origin.function', + type: 'keyword', + }, + 'log.original': { + category: 'log', + description: + "This is the original log message and contains the full log message before splitting it up in multiple parts. In contrast to the `message` field which can contain an extracted part of the log message, this field contains the original, full log message. It can have already some modifications applied like encoding or new lines removed to clean up the log message. This field is not indexed and doc_values are disabled so it can't be queried but the value can be retrieved from `_source`.", + example: 'Sep 19 08:26:10 localhost My log', + name: 'log.original', + type: 'keyword', + }, + 'log.syslog': { + category: 'log', + description: + 'The Syslog metadata of the event, if the event was transmitted via Syslog. Please see RFCs 5424 or 3164.', + name: 'log.syslog', + type: 'object', + }, + 'log.syslog.facility.code': { + category: 'log', + description: + 'The Syslog numeric facility of the log event, if available. According to RFCs 5424 and 3164, this value should be an integer between 0 and 23.', + example: 23, + name: 'log.syslog.facility.code', + type: 'long', + format: 'string', + }, + 'log.syslog.facility.name': { + category: 'log', + description: 'The Syslog text-based facility of the log event, if available.', + example: 'local7', + name: 'log.syslog.facility.name', + type: 'keyword', + }, + 'log.syslog.priority': { + category: 'log', + description: + 'Syslog numeric priority of the event, if available. According to RFCs 5424 and 3164, the priority is 8 * facility + severity. This number is therefore expected to contain a value between 0 and 191.', + example: 135, + name: 'log.syslog.priority', + type: 'long', + format: 'string', + }, + 'log.syslog.severity.code': { + category: 'log', + description: + "The Syslog numeric severity of the log event, if available. If the event source publishing via Syslog provides a different numeric severity value (e.g. firewall, IDS), your source's numeric severity should go to `event.severity`. If the event source does not specify a distinct severity, you can optionally copy the Syslog severity to `event.severity`.", + example: 3, + name: 'log.syslog.severity.code', + type: 'long', + }, + 'log.syslog.severity.name': { + category: 'log', + description: + "The Syslog numeric severity of the log event, if available. If the event source publishing via Syslog provides a different severity value (e.g. firewall, IDS), your source's text severity should go to `log.level`. If the event source does not specify a distinct severity, you can optionally copy the Syslog severity to `log.level`.", + example: 'Error', + name: 'log.syslog.severity.name', + type: 'keyword', + }, + 'network.application': { + category: 'network', + description: + 'A name given to an application level protocol. This can be arbitrarily assigned for things like microservices, but also apply to things like skype, icq, facebook, twitter. This would be used in situations where the vendor or service can be decoded such as from the source/dest IP owners, ports, or wire format. The field value must be normalized to lowercase for querying. See the documentation section "Implementing ECS".', + example: 'aim', + name: 'network.application', + type: 'keyword', + }, + 'network.bytes': { + category: 'network', + description: + 'Total bytes transferred in both directions. If `source.bytes` and `destination.bytes` are known, `network.bytes` is their sum.', + example: 368, + name: 'network.bytes', + type: 'long', + format: 'bytes', + }, + 'network.community_id': { + category: 'network', + description: + 'A hash of source and destination IPs and ports, as well as the protocol used in a communication. This is a tool-agnostic standard to identify flows. Learn more at https://github.com/corelight/community-id-spec.', + example: '1:hO+sN4H+MG5MY/8hIrXPqc4ZQz0=', + name: 'network.community_id', + type: 'keyword', + }, + 'network.direction': { + category: 'network', + description: + "Direction of the network traffic. Recommended values are: * inbound * outbound * internal * external * unknown When mapping events from a host-based monitoring context, populate this field from the host's point of view. When mapping events from a network or perimeter-based monitoring context, populate this field from the point of view of your network perimeter.", + example: 'inbound', + name: 'network.direction', + type: 'keyword', + }, + 'network.forwarded_ip': { + category: 'network', + description: 'Host IP address when the source IP address is the proxy.', + example: '192.1.1.2', + name: 'network.forwarded_ip', + type: 'ip', + }, + 'network.iana_number': { + category: 'network', + description: + 'IANA Protocol Number (https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml). Standardized list of protocols. This aligns well with NetFlow and sFlow related logs which use the IANA Protocol Number.', + example: 6, + name: 'network.iana_number', + type: 'keyword', + }, + 'network.inner': { + category: 'network', + description: + 'Network.inner fields are added in addition to network.vlan fields to describe the innermost VLAN when q-in-q VLAN tagging is present. Allowed fields include vlan.id and vlan.name. Inner vlan fields are typically used when sending traffic with multiple 802.1q encapsulations to a network sensor (e.g. Zeek, Wireshark.)', + name: 'network.inner', + type: 'object', + }, + 'network.inner.vlan.id': { + category: 'network', + description: 'VLAN ID as reported by the observer.', + example: 10, + name: 'network.inner.vlan.id', + type: 'keyword', + }, + 'network.inner.vlan.name': { + category: 'network', + description: 'Optional VLAN name as reported by the observer.', + example: 'outside', + name: 'network.inner.vlan.name', + type: 'keyword', + }, + 'network.name': { + category: 'network', + description: 'Name given by operators to sections of their network.', + example: 'Guest Wifi', + name: 'network.name', + type: 'keyword', + }, + 'network.packets': { + category: 'network', + description: + 'Total packets transferred in both directions. If `source.packets` and `destination.packets` are known, `network.packets` is their sum.', + example: 24, + name: 'network.packets', + type: 'long', + }, + 'network.protocol': { + category: 'network', + description: + 'L7 Network protocol name. ex. http, lumberjack, transport protocol. The field value must be normalized to lowercase for querying. See the documentation section "Implementing ECS".', + example: 'http', + name: 'network.protocol', + type: 'keyword', + }, + 'network.transport': { + category: 'network', + description: + 'Same as network.iana_number, but instead using the Keyword name of the transport layer (udp, tcp, ipv6-icmp, etc.) The field value must be normalized to lowercase for querying. See the documentation section "Implementing ECS".', + example: 'tcp', + name: 'network.transport', + type: 'keyword', + }, + 'network.type': { + category: 'network', + description: + 'In the OSI Model this would be the Network Layer. ipv4, ipv6, ipsec, pim, etc The field value must be normalized to lowercase for querying. See the documentation section "Implementing ECS".', + example: 'ipv4', + name: 'network.type', + type: 'keyword', + }, + 'network.vlan.id': { + category: 'network', + description: 'VLAN ID as reported by the observer.', + example: 10, + name: 'network.vlan.id', + type: 'keyword', + }, + 'network.vlan.name': { + category: 'network', + description: 'Optional VLAN name as reported by the observer.', + example: 'outside', + name: 'network.vlan.name', + type: 'keyword', + }, + 'observer.egress': { + category: 'observer', + description: + 'Observer.egress holds information like interface number and name, vlan, and zone information to classify egress traffic. Single armed monitoring such as a network sensor on a span port should only use observer.ingress to categorize traffic.', + name: 'observer.egress', + type: 'object', + }, + 'observer.egress.interface.alias': { + category: 'observer', + description: + 'Interface alias as reported by the system, typically used in firewall implementations for e.g. inside, outside, or dmz logical interface naming.', + example: 'outside', + name: 'observer.egress.interface.alias', + type: 'keyword', + }, + 'observer.egress.interface.id': { + category: 'observer', + description: 'Interface ID as reported by an observer (typically SNMP interface ID).', + example: 10, + name: 'observer.egress.interface.id', + type: 'keyword', + }, + 'observer.egress.interface.name': { + category: 'observer', + description: 'Interface name as reported by the system.', + example: 'eth0', + name: 'observer.egress.interface.name', + type: 'keyword', + }, + 'observer.egress.vlan.id': { + category: 'observer', + description: 'VLAN ID as reported by the observer.', + example: 10, + name: 'observer.egress.vlan.id', + type: 'keyword', + }, + 'observer.egress.vlan.name': { + category: 'observer', + description: 'Optional VLAN name as reported by the observer.', + example: 'outside', + name: 'observer.egress.vlan.name', + type: 'keyword', + }, + 'observer.egress.zone': { + category: 'observer', + description: + 'Network zone of outbound traffic as reported by the observer to categorize the destination area of egress traffic, e.g. Internal, External, DMZ, HR, Legal, etc.', + example: 'Public_Internet', + name: 'observer.egress.zone', + type: 'keyword', + }, + 'observer.geo.city_name': { + category: 'observer', + description: 'City name.', + example: 'Montreal', + name: 'observer.geo.city_name', + type: 'keyword', + }, + 'observer.geo.continent_name': { + category: 'observer', + description: 'Name of the continent.', + example: 'North America', + name: 'observer.geo.continent_name', + type: 'keyword', + }, + 'observer.geo.country_iso_code': { + category: 'observer', + description: 'Country ISO code.', + example: 'CA', + name: 'observer.geo.country_iso_code', + type: 'keyword', + }, + 'observer.geo.country_name': { + category: 'observer', + description: 'Country name.', + example: 'Canada', + name: 'observer.geo.country_name', + type: 'keyword', + }, + 'observer.geo.location': { + category: 'observer', + description: 'Longitude and latitude.', + example: '{ "lon": -73.614830, "lat": 45.505918 }', + name: 'observer.geo.location', + type: 'geo_point', + }, + 'observer.geo.name': { + category: 'observer', + description: + 'User-defined description of a location, at the level of granularity they care about. Could be the name of their data centers, the floor number, if this describes a local physical entity, city names. Not typically used in automated geolocation.', + example: 'boston-dc', + name: 'observer.geo.name', + type: 'keyword', + }, + 'observer.geo.region_iso_code': { + category: 'observer', + description: 'Region ISO code.', + example: 'CA-QC', + name: 'observer.geo.region_iso_code', + type: 'keyword', + }, + 'observer.geo.region_name': { + category: 'observer', + description: 'Region name.', + example: 'Quebec', + name: 'observer.geo.region_name', + type: 'keyword', + }, + 'observer.hostname': { + category: 'observer', + description: 'Hostname of the observer.', + name: 'observer.hostname', + type: 'keyword', + }, + 'observer.ingress': { + category: 'observer', + description: + 'Observer.ingress holds information like interface number and name, vlan, and zone information to classify ingress traffic. Single armed monitoring such as a network sensor on a span port should only use observer.ingress to categorize traffic.', + name: 'observer.ingress', + type: 'object', + }, + 'observer.ingress.interface.alias': { + category: 'observer', + description: + 'Interface alias as reported by the system, typically used in firewall implementations for e.g. inside, outside, or dmz logical interface naming.', + example: 'outside', + name: 'observer.ingress.interface.alias', + type: 'keyword', + }, + 'observer.ingress.interface.id': { + category: 'observer', + description: 'Interface ID as reported by an observer (typically SNMP interface ID).', + example: 10, + name: 'observer.ingress.interface.id', + type: 'keyword', + }, + 'observer.ingress.interface.name': { + category: 'observer', + description: 'Interface name as reported by the system.', + example: 'eth0', + name: 'observer.ingress.interface.name', + type: 'keyword', + }, + 'observer.ingress.vlan.id': { + category: 'observer', + description: 'VLAN ID as reported by the observer.', + example: 10, + name: 'observer.ingress.vlan.id', + type: 'keyword', + }, + 'observer.ingress.vlan.name': { + category: 'observer', + description: 'Optional VLAN name as reported by the observer.', + example: 'outside', + name: 'observer.ingress.vlan.name', + type: 'keyword', + }, + 'observer.ingress.zone': { + category: 'observer', + description: + 'Network zone of incoming traffic as reported by the observer to categorize the source area of ingress traffic. e.g. internal, External, DMZ, HR, Legal, etc.', + example: 'DMZ', + name: 'observer.ingress.zone', + type: 'keyword', + }, + 'observer.ip': { + category: 'observer', + description: 'IP addresses of the observer.', + name: 'observer.ip', + type: 'ip', + }, + 'observer.mac': { + category: 'observer', + description: 'MAC addresses of the observer', + name: 'observer.mac', + type: 'keyword', + }, + 'observer.name': { + category: 'observer', + description: + 'Custom name of the observer. This is a name that can be given to an observer. This can be helpful for example if multiple firewalls of the same model are used in an organization. If no custom name is needed, the field can be left empty.', + example: '1_proxySG', + name: 'observer.name', + type: 'keyword', + }, + 'observer.os.family': { + category: 'observer', + description: 'OS family (such as redhat, debian, freebsd, windows).', + example: 'debian', + name: 'observer.os.family', + type: 'keyword', + }, + 'observer.os.full': { + category: 'observer', + description: 'Operating system name, including the version or code name.', + example: 'Mac OS Mojave', + name: 'observer.os.full', + type: 'keyword', + }, + 'observer.os.kernel': { + category: 'observer', + description: 'Operating system kernel version as a raw string.', + example: '4.4.0-112-generic', + name: 'observer.os.kernel', + type: 'keyword', + }, + 'observer.os.name': { + category: 'observer', + description: 'Operating system name, without the version.', + example: 'Mac OS X', + name: 'observer.os.name', + type: 'keyword', + }, + 'observer.os.platform': { + category: 'observer', + description: 'Operating system platform (such centos, ubuntu, windows).', + example: 'darwin', + name: 'observer.os.platform', + type: 'keyword', + }, + 'observer.os.version': { + category: 'observer', + description: 'Operating system version as a raw string.', + example: '10.14.1', + name: 'observer.os.version', + type: 'keyword', + }, + 'observer.product': { + category: 'observer', + description: 'The product name of the observer.', + example: 's200', + name: 'observer.product', + type: 'keyword', + }, + 'observer.serial_number': { + category: 'observer', + description: 'Observer serial number.', + name: 'observer.serial_number', + type: 'keyword', + }, + 'observer.type': { + category: 'observer', + description: + 'The type of the observer the data is coming from. There is no predefined list of observer types. Some examples are `forwarder`, `firewall`, `ids`, `ips`, `proxy`, `poller`, `sensor`, `APM server`.', + example: 'firewall', + name: 'observer.type', + type: 'keyword', + }, + 'observer.vendor': { + category: 'observer', + description: 'Vendor name of the observer.', + example: 'Symantec', + name: 'observer.vendor', + type: 'keyword', + }, + 'observer.version': { + category: 'observer', + description: 'Observer version.', + name: 'observer.version', + type: 'keyword', + }, + 'organization.id': { + category: 'organization', + description: 'Unique identifier for the organization.', + name: 'organization.id', + type: 'keyword', + }, + 'organization.name': { + category: 'organization', + description: 'Organization name.', + name: 'organization.name', + type: 'keyword', + }, + 'os.family': { + category: 'os', + description: 'OS family (such as redhat, debian, freebsd, windows).', + example: 'debian', + name: 'os.family', + type: 'keyword', + }, + 'os.full': { + category: 'os', + description: 'Operating system name, including the version or code name.', + example: 'Mac OS Mojave', + name: 'os.full', + type: 'keyword', + }, + 'os.kernel': { + category: 'os', + description: 'Operating system kernel version as a raw string.', + example: '4.4.0-112-generic', + name: 'os.kernel', + type: 'keyword', + }, + 'os.name': { + category: 'os', + description: 'Operating system name, without the version.', + example: 'Mac OS X', + name: 'os.name', + type: 'keyword', + }, + 'os.platform': { + category: 'os', + description: 'Operating system platform (such centos, ubuntu, windows).', + example: 'darwin', + name: 'os.platform', + type: 'keyword', + }, + 'os.version': { + category: 'os', + description: 'Operating system version as a raw string.', + example: '10.14.1', + name: 'os.version', + type: 'keyword', + }, + 'package.architecture': { + category: 'package', + description: 'Package architecture.', + example: 'x86_64', + name: 'package.architecture', + type: 'keyword', + }, + 'package.build_version': { + category: 'package', + description: + 'Additional information about the build version of the installed package. For example use the commit SHA of a non-released package.', + example: '36f4f7e89dd61b0988b12ee000b98966867710cd', + name: 'package.build_version', + type: 'keyword', + }, + 'package.checksum': { + category: 'package', + description: 'Checksum of the installed package for verification.', + example: '68b329da9893e34099c7d8ad5cb9c940', + name: 'package.checksum', + type: 'keyword', + }, + 'package.description': { + category: 'package', + description: 'Description of the package.', + example: 'Open source programming language to build simple/reliable/efficient software.', + name: 'package.description', + type: 'keyword', + }, + 'package.install_scope': { + category: 'package', + description: 'Indicating how the package was installed, e.g. user-local, global.', + example: 'global', + name: 'package.install_scope', + type: 'keyword', + }, + 'package.installed': { + category: 'package', + description: 'Time when package was installed.', + name: 'package.installed', + type: 'date', + }, + 'package.license': { + category: 'package', + description: + 'License under which the package was released. Use a short name, e.g. the license identifier from SPDX License List where possible (https://spdx.org/licenses/).', + example: 'Apache License 2.0', + name: 'package.license', + type: 'keyword', + }, + 'package.name': { + category: 'package', + description: 'Package name', + example: 'go', + name: 'package.name', + type: 'keyword', + }, + 'package.path': { + category: 'package', + description: 'Path where the package is installed.', + example: '/usr/local/Cellar/go/1.12.9/', + name: 'package.path', + type: 'keyword', + }, + 'package.reference': { + category: 'package', + description: 'Home page or reference URL of the software in this package, if available.', + example: 'https://golang.org', + name: 'package.reference', + type: 'keyword', + }, + 'package.size': { + category: 'package', + description: 'Package size in bytes.', + example: 62231, + name: 'package.size', + type: 'long', + format: 'string', + }, + 'package.type': { + category: 'package', + description: + 'Type of package. This should contain the package file type, rather than the package manager name. Examples: rpm, dpkg, brew, npm, gem, nupkg, jar.', + example: 'rpm', + name: 'package.type', + type: 'keyword', + }, + 'package.version': { + category: 'package', + description: 'Package version', + example: '1.12.9', + name: 'package.version', + type: 'keyword', + }, + 'pe.company': { + category: 'pe', + description: 'Internal company name of the file, provided at compile-time.', + example: 'Microsoft Corporation', + name: 'pe.company', + type: 'keyword', + }, + 'pe.description': { + category: 'pe', + description: 'Internal description of the file, provided at compile-time.', + example: 'Paint', + name: 'pe.description', + type: 'keyword', + }, + 'pe.file_version': { + category: 'pe', + description: 'Internal version of the file, provided at compile-time.', + example: '6.3.9600.17415', + name: 'pe.file_version', + type: 'keyword', + }, + 'pe.original_file_name': { + category: 'pe', + description: 'Internal name of the file, provided at compile-time.', + example: 'MSPAINT.EXE', + name: 'pe.original_file_name', + type: 'keyword', + }, + 'pe.product': { + category: 'pe', + description: 'Internal product name of the file, provided at compile-time.', + example: 'Microsoft® Windows® Operating System', + name: 'pe.product', + type: 'keyword', + }, + 'process.args': { + category: 'process', + description: + 'Array of process arguments, starting with the absolute path to the executable. May be filtered to protect sensitive information.', + example: '["/usr/bin/ssh","-l","user","10.0.0.16"]', + name: 'process.args', + type: 'keyword', + }, + 'process.args_count': { + category: 'process', + description: + 'Length of the process.args array. This field can be useful for querying or performing bucket analysis on how many arguments were provided to start a process. More arguments may be an indication of suspicious activity.', + example: 4, + name: 'process.args_count', + type: 'long', + }, + 'process.code_signature.exists': { + category: 'process', + description: 'Boolean to capture if a signature is present.', + example: 'true', + name: 'process.code_signature.exists', + type: 'boolean', + }, + 'process.code_signature.status': { + category: 'process', + description: + 'Additional information about the certificate status. This is useful for logging cryptographic errors with the certificate validity or trust status. Leave unpopulated if the validity or trust of the certificate was unchecked.', + example: 'ERROR_UNTRUSTED_ROOT', + name: 'process.code_signature.status', + type: 'keyword', + }, + 'process.code_signature.subject_name': { + category: 'process', + description: 'Subject name of the code signer', + example: 'Microsoft Corporation', + name: 'process.code_signature.subject_name', + type: 'keyword', + }, + 'process.code_signature.trusted': { + category: 'process', + description: + 'Stores the trust status of the certificate chain. Validating the trust of the certificate chain may be complicated, and this field should only be populated by tools that actively check the status.', + example: 'true', + name: 'process.code_signature.trusted', + type: 'boolean', + }, + 'process.code_signature.valid': { + category: 'process', + description: + 'Boolean to capture if the digital signature is verified against the binary content. Leave unpopulated if a certificate was unchecked.', + example: 'true', + name: 'process.code_signature.valid', + type: 'boolean', + }, + 'process.command_line': { + category: 'process', + description: + 'Full command line that started the process, including the absolute path to the executable, and all arguments. Some arguments may be filtered to protect sensitive information.', + example: '/usr/bin/ssh -l user 10.0.0.16', + name: 'process.command_line', + type: 'keyword', + }, + 'process.entity_id': { + category: 'process', + description: + 'Unique identifier for the process. The implementation of this is specified by the data source, but some examples of what could be used here are a process-generated UUID, Sysmon Process GUIDs, or a hash of some uniquely identifying components of a process. Constructing a globally unique identifier is a common practice to mitigate PID reuse as well as to identify a specific process over time, across multiple monitored hosts.', + example: 'c2c455d9f99375d', + name: 'process.entity_id', + type: 'keyword', + }, + 'process.executable': { + category: 'process', + description: 'Absolute path to the process executable.', + example: '/usr/bin/ssh', + name: 'process.executable', + type: 'keyword', + }, + 'process.exit_code': { + category: 'process', + description: + 'The exit code of the process, if this is a termination event. The field should be absent if there is no exit code for the event (e.g. process start).', + example: 137, + name: 'process.exit_code', + type: 'long', + }, + 'process.hash.md5': { + category: 'process', + description: 'MD5 hash.', + name: 'process.hash.md5', + type: 'keyword', + }, + 'process.hash.sha1': { + category: 'process', + description: 'SHA1 hash.', + name: 'process.hash.sha1', + type: 'keyword', + }, + 'process.hash.sha256': { + category: 'process', + description: 'SHA256 hash.', + name: 'process.hash.sha256', + type: 'keyword', + }, + 'process.hash.sha512': { + category: 'process', + description: 'SHA512 hash.', + name: 'process.hash.sha512', + type: 'keyword', + }, + 'process.name': { + category: 'process', + description: 'Process name. Sometimes called program name or similar.', + example: 'ssh', + name: 'process.name', + type: 'keyword', + }, + 'process.parent.args': { + category: 'process', + description: 'Array of process arguments. May be filtered to protect sensitive information.', + example: '["ssh","-l","user","10.0.0.16"]', + name: 'process.parent.args', + type: 'keyword', + }, + 'process.parent.args_count': { + category: 'process', + description: + 'Length of the process.args array. This field can be useful for querying or performing bucket analysis on how many arguments were provided to start a process. More arguments may be an indication of suspicious activity.', + example: 4, + name: 'process.parent.args_count', + type: 'long', + }, + 'process.parent.code_signature.exists': { + category: 'process', + description: 'Boolean to capture if a signature is present.', + example: 'true', + name: 'process.parent.code_signature.exists', + type: 'boolean', + }, + 'process.parent.code_signature.status': { + category: 'process', + description: + 'Additional information about the certificate status. This is useful for logging cryptographic errors with the certificate validity or trust status. Leave unpopulated if the validity or trust of the certificate was unchecked.', + example: 'ERROR_UNTRUSTED_ROOT', + name: 'process.parent.code_signature.status', + type: 'keyword', + }, + 'process.parent.code_signature.subject_name': { + category: 'process', + description: 'Subject name of the code signer', + example: 'Microsoft Corporation', + name: 'process.parent.code_signature.subject_name', + type: 'keyword', + }, + 'process.parent.code_signature.trusted': { + category: 'process', + description: + 'Stores the trust status of the certificate chain. Validating the trust of the certificate chain may be complicated, and this field should only be populated by tools that actively check the status.', + example: 'true', + name: 'process.parent.code_signature.trusted', + type: 'boolean', + }, + 'process.parent.code_signature.valid': { + category: 'process', + description: + 'Boolean to capture if the digital signature is verified against the binary content. Leave unpopulated if a certificate was unchecked.', + example: 'true', + name: 'process.parent.code_signature.valid', + type: 'boolean', + }, + 'process.parent.command_line': { + category: 'process', + description: + 'Full command line that started the process, including the absolute path to the executable, and all arguments. Some arguments may be filtered to protect sensitive information.', + example: '/usr/bin/ssh -l user 10.0.0.16', + name: 'process.parent.command_line', + type: 'keyword', + }, + 'process.parent.entity_id': { + category: 'process', + description: + 'Unique identifier for the process. The implementation of this is specified by the data source, but some examples of what could be used here are a process-generated UUID, Sysmon Process GUIDs, or a hash of some uniquely identifying components of a process. Constructing a globally unique identifier is a common practice to mitigate PID reuse as well as to identify a specific process over time, across multiple monitored hosts.', + example: 'c2c455d9f99375d', + name: 'process.parent.entity_id', + type: 'keyword', + }, + 'process.parent.executable': { + category: 'process', + description: 'Absolute path to the process executable.', + example: '/usr/bin/ssh', + name: 'process.parent.executable', + type: 'keyword', + }, + 'process.parent.exit_code': { + category: 'process', + description: + 'The exit code of the process, if this is a termination event. The field should be absent if there is no exit code for the event (e.g. process start).', + example: 137, + name: 'process.parent.exit_code', + type: 'long', + }, + 'process.parent.hash.md5': { + category: 'process', + description: 'MD5 hash.', + name: 'process.parent.hash.md5', + type: 'keyword', + }, + 'process.parent.hash.sha1': { + category: 'process', + description: 'SHA1 hash.', + name: 'process.parent.hash.sha1', + type: 'keyword', + }, + 'process.parent.hash.sha256': { + category: 'process', + description: 'SHA256 hash.', + name: 'process.parent.hash.sha256', + type: 'keyword', + }, + 'process.parent.hash.sha512': { + category: 'process', + description: 'SHA512 hash.', + name: 'process.parent.hash.sha512', + type: 'keyword', + }, + 'process.parent.name': { + category: 'process', + description: 'Process name. Sometimes called program name or similar.', + example: 'ssh', + name: 'process.parent.name', + type: 'keyword', + }, + 'process.parent.pgid': { + category: 'process', + description: 'Identifier of the group of processes the process belongs to.', + name: 'process.parent.pgid', + type: 'long', + format: 'string', + }, + 'process.parent.pid': { + category: 'process', + description: 'Process id.', + example: 4242, + name: 'process.parent.pid', + type: 'long', + format: 'string', + }, + 'process.parent.ppid': { + category: 'process', + description: "Parent process' pid.", + example: 4241, + name: 'process.parent.ppid', + type: 'long', + format: 'string', + }, + 'process.parent.start': { + category: 'process', + description: 'The time the process started.', + example: '2016-05-23T08:05:34.853Z', + name: 'process.parent.start', + type: 'date', + }, + 'process.parent.thread.id': { + category: 'process', + description: 'Thread ID.', + example: 4242, + name: 'process.parent.thread.id', + type: 'long', + format: 'string', + }, + 'process.parent.thread.name': { + category: 'process', + description: 'Thread name.', + example: 'thread-0', + name: 'process.parent.thread.name', + type: 'keyword', + }, + 'process.parent.title': { + category: 'process', + description: + 'Process title. The proctitle, some times the same as process name. Can also be different: for example a browser setting its title to the web page currently opened.', + name: 'process.parent.title', + type: 'keyword', + }, + 'process.parent.uptime': { + category: 'process', + description: 'Seconds the process has been up.', + example: 1325, + name: 'process.parent.uptime', + type: 'long', + }, + 'process.parent.working_directory': { + category: 'process', + description: 'The working directory of the process.', + example: '/home/alice', + name: 'process.parent.working_directory', + type: 'keyword', + }, + 'process.pe.company': { + category: 'process', + description: 'Internal company name of the file, provided at compile-time.', + example: 'Microsoft Corporation', + name: 'process.pe.company', + type: 'keyword', + }, + 'process.pe.description': { + category: 'process', + description: 'Internal description of the file, provided at compile-time.', + example: 'Paint', + name: 'process.pe.description', + type: 'keyword', + }, + 'process.pe.file_version': { + category: 'process', + description: 'Internal version of the file, provided at compile-time.', + example: '6.3.9600.17415', + name: 'process.pe.file_version', + type: 'keyword', + }, + 'process.pe.original_file_name': { + category: 'process', + description: 'Internal name of the file, provided at compile-time.', + example: 'MSPAINT.EXE', + name: 'process.pe.original_file_name', + type: 'keyword', + }, + 'process.pe.product': { + category: 'process', + description: 'Internal product name of the file, provided at compile-time.', + example: 'Microsoft® Windows® Operating System', + name: 'process.pe.product', + type: 'keyword', + }, + 'process.pgid': { + category: 'process', + description: 'Identifier of the group of processes the process belongs to.', + name: 'process.pgid', + type: 'long', + format: 'string', + }, + 'process.pid': { + category: 'process', + description: 'Process id.', + example: 4242, + name: 'process.pid', + type: 'long', + format: 'string', + }, + 'process.ppid': { + category: 'process', + description: "Parent process' pid.", + example: 4241, + name: 'process.ppid', + type: 'long', + format: 'string', + }, + 'process.start': { + category: 'process', + description: 'The time the process started.', + example: '2016-05-23T08:05:34.853Z', + name: 'process.start', + type: 'date', + }, + 'process.thread.id': { + category: 'process', + description: 'Thread ID.', + example: 4242, + name: 'process.thread.id', + type: 'long', + format: 'string', + }, + 'process.thread.name': { + category: 'process', + description: 'Thread name.', + example: 'thread-0', + name: 'process.thread.name', + type: 'keyword', + }, + 'process.title': { + category: 'process', + description: + 'Process title. The proctitle, some times the same as process name. Can also be different: for example a browser setting its title to the web page currently opened.', + name: 'process.title', + type: 'keyword', + }, + 'process.uptime': { + category: 'process', + description: 'Seconds the process has been up.', + example: 1325, + name: 'process.uptime', + type: 'long', + }, + 'process.working_directory': { + category: 'process', + description: 'The working directory of the process.', + example: '/home/alice', + name: 'process.working_directory', + type: 'keyword', + }, + 'registry.data.bytes': { + category: 'registry', + description: + 'Original bytes written with base64 encoding. For Windows registry operations, such as SetValueEx and RegQueryValueEx, this corresponds to the data pointed by `lp_data`. This is optional but provides better recoverability and should be populated for REG_BINARY encoded values.', + example: 'ZQBuAC0AVQBTAAAAZQBuAAAAAAA=', + name: 'registry.data.bytes', + type: 'keyword', + }, + 'registry.data.strings': { + category: 'registry', + description: + 'Content when writing string types. Populated as an array when writing string data to the registry. For single string registry types (REG_SZ, REG_EXPAND_SZ), this should be an array with one string. For sequences of string with REG_MULTI_SZ, this array will be variable length. For numeric data, such as REG_DWORD and REG_QWORD, this should be populated with the decimal representation (e.g `"1"`).', + example: '["C:\\rta\\red_ttp\\bin\\myapp.exe"]', + name: 'registry.data.strings', + type: 'keyword', + }, + 'registry.data.type': { + category: 'registry', + description: 'Standard registry type for encoding contents', + example: 'REG_SZ', + name: 'registry.data.type', + type: 'keyword', + }, + 'registry.hive': { + category: 'registry', + description: 'Abbreviated name for the hive.', + example: 'HKLM', + name: 'registry.hive', + type: 'keyword', + }, + 'registry.key': { + category: 'registry', + description: 'Hive-relative path of keys.', + example: + 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\winword.exe', + name: 'registry.key', + type: 'keyword', + }, + 'registry.path': { + category: 'registry', + description: 'Full path, including hive, key and value', + example: + 'HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\winword.exe\\Debugger', + name: 'registry.path', + type: 'keyword', + }, + 'registry.value': { + category: 'registry', + description: 'Name of the value written.', + example: 'Debugger', + name: 'registry.value', + type: 'keyword', + }, + 'related.hash': { + category: 'related', + description: + "All the hashes seen on your event. Populating this field, then using it to search for hashes can help in situations where you're unsure what the hash algorithm is (and therefore which key name to search).", + name: 'related.hash', + type: 'keyword', + }, + 'related.ip': { + category: 'related', + description: 'All of the IPs seen on your event.', + name: 'related.ip', + type: 'ip', + }, + 'related.user': { + category: 'related', + description: 'All the user names seen on your event.', + name: 'related.user', + type: 'keyword', + }, + 'rule.author': { + category: 'rule', + description: + 'Name, organization, or pseudonym of the author or authors who created the rule used to generate this event.', + example: '["Star-Lord"]', + name: 'rule.author', + type: 'keyword', + }, + 'rule.category': { + category: 'rule', + description: + 'A categorization value keyword used by the entity using the rule for detection of this event.', + example: 'Attempted Information Leak', + name: 'rule.category', + type: 'keyword', + }, + 'rule.description': { + category: 'rule', + description: 'The description of the rule generating the event.', + example: 'Block requests to public DNS over HTTPS / TLS protocols', + name: 'rule.description', + type: 'keyword', + }, + 'rule.id': { + category: 'rule', + description: + 'A rule ID that is unique within the scope of an agent, observer, or other entity using the rule for detection of this event.', + example: 101, + name: 'rule.id', + type: 'keyword', + }, + 'rule.license': { + category: 'rule', + description: + 'Name of the license under which the rule used to generate this event is made available.', + example: 'Apache 2.0', + name: 'rule.license', + type: 'keyword', + }, + 'rule.name': { + category: 'rule', + description: 'The name of the rule or signature generating the event.', + example: 'BLOCK_DNS_over_TLS', + name: 'rule.name', + type: 'keyword', + }, + 'rule.reference': { + category: 'rule', + description: + "Reference URL to additional information about the rule used to generate this event. The URL can point to the vendor's documentation about the rule. If that's not available, it can also be a link to a more general page describing this type of alert.", + example: 'https://en.wikipedia.org/wiki/DNS_over_TLS', + name: 'rule.reference', + type: 'keyword', + }, + 'rule.ruleset': { + category: 'rule', + description: + 'Name of the ruleset, policy, group, or parent category in which the rule used to generate this event is a member.', + example: 'Standard_Protocol_Filters', + name: 'rule.ruleset', + type: 'keyword', + }, + 'rule.uuid': { + category: 'rule', + description: + 'A rule ID that is unique within the scope of a set or group of agents, observers, or other entities using the rule for detection of this event.', + example: 1100110011, + name: 'rule.uuid', + type: 'keyword', + }, + 'rule.version': { + category: 'rule', + description: 'The version / revision of the rule being used for analysis.', + example: 1.1, + name: 'rule.version', + type: 'keyword', + }, + 'server.address': { + category: 'server', + description: + 'Some event server addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the `.address` field. Then it should be duplicated to `.ip` or `.domain`, depending on which one it is.', + name: 'server.address', + type: 'keyword', + }, + 'server.as.number': { + category: 'server', + description: + 'Unique number allocated to the autonomous system. The autonomous system number (ASN) uniquely identifies each network on the Internet.', + example: 15169, + name: 'server.as.number', + type: 'long', + }, + 'server.as.organization.name': { + category: 'server', + description: 'Organization name.', + example: 'Google LLC', + name: 'server.as.organization.name', + type: 'keyword', + }, + 'server.bytes': { + category: 'server', + description: 'Bytes sent from the server to the client.', + example: 184, + name: 'server.bytes', + type: 'long', + format: 'bytes', + }, + 'server.domain': { + category: 'server', + description: 'Server domain.', + name: 'server.domain', + type: 'keyword', + }, + 'server.geo.city_name': { + category: 'server', + description: 'City name.', + example: 'Montreal', + name: 'server.geo.city_name', + type: 'keyword', + }, + 'server.geo.continent_name': { + category: 'server', + description: 'Name of the continent.', + example: 'North America', + name: 'server.geo.continent_name', + type: 'keyword', + }, + 'server.geo.country_iso_code': { + category: 'server', + description: 'Country ISO code.', + example: 'CA', + name: 'server.geo.country_iso_code', + type: 'keyword', + }, + 'server.geo.country_name': { + category: 'server', + description: 'Country name.', + example: 'Canada', + name: 'server.geo.country_name', + type: 'keyword', + }, + 'server.geo.location': { + category: 'server', + description: 'Longitude and latitude.', + example: '{ "lon": -73.614830, "lat": 45.505918 }', + name: 'server.geo.location', + type: 'geo_point', + }, + 'server.geo.name': { + category: 'server', + description: + 'User-defined description of a location, at the level of granularity they care about. Could be the name of their data centers, the floor number, if this describes a local physical entity, city names. Not typically used in automated geolocation.', + example: 'boston-dc', + name: 'server.geo.name', + type: 'keyword', + }, + 'server.geo.region_iso_code': { + category: 'server', + description: 'Region ISO code.', + example: 'CA-QC', + name: 'server.geo.region_iso_code', + type: 'keyword', + }, + 'server.geo.region_name': { + category: 'server', + description: 'Region name.', + example: 'Quebec', + name: 'server.geo.region_name', + type: 'keyword', + }, + 'server.ip': { + category: 'server', + description: 'IP address of the server. Can be one or multiple IPv4 or IPv6 addresses.', + name: 'server.ip', + type: 'ip', + }, + 'server.mac': { + category: 'server', + description: 'MAC address of the server.', + name: 'server.mac', + type: 'keyword', + }, + 'server.nat.ip': { + category: 'server', + description: + 'Translated ip of destination based NAT sessions (e.g. internet to private DMZ) Typically used with load balancers, firewalls, or routers.', + name: 'server.nat.ip', + type: 'ip', + }, + 'server.nat.port': { + category: 'server', + description: + 'Translated port of destination based NAT sessions (e.g. internet to private DMZ) Typically used with load balancers, firewalls, or routers.', + name: 'server.nat.port', + type: 'long', + format: 'string', + }, + 'server.packets': { + category: 'server', + description: 'Packets sent from the server to the client.', + example: 12, + name: 'server.packets', + type: 'long', + }, + 'server.port': { + category: 'server', + description: 'Port of the server.', + name: 'server.port', + type: 'long', + format: 'string', + }, + 'server.registered_domain': { + category: 'server', + description: + 'The highest registered server domain, stripped of the subdomain. For example, the registered domain for "foo.google.com" is "google.com". This value can be determined precisely with a list like the public suffix list (http://publicsuffix.org). Trying to approximate this by simply taking the last two labels will not work well for TLDs such as "co.uk".', + example: 'google.com', + name: 'server.registered_domain', + type: 'keyword', + }, + 'server.top_level_domain': { + category: 'server', + description: + 'The effective top level domain (eTLD), also known as the domain suffix, is the last part of the domain name. For example, the top level domain for google.com is "com". This value can be determined precisely with a list like the public suffix list (http://publicsuffix.org). Trying to approximate this by simply taking the last label will not work well for effective TLDs such as "co.uk".', + example: 'co.uk', + name: 'server.top_level_domain', + type: 'keyword', + }, + 'server.user.domain': { + category: 'server', + description: + 'Name of the directory the user is a member of. For example, an LDAP or Active Directory domain name.', + name: 'server.user.domain', + type: 'keyword', + }, + 'server.user.email': { + category: 'server', + description: 'User email address.', + name: 'server.user.email', + type: 'keyword', + }, + 'server.user.full_name': { + category: 'server', + description: "User's full name, if available.", + example: 'Albert Einstein', + name: 'server.user.full_name', + type: 'keyword', + }, + 'server.user.group.domain': { + category: 'server', + description: + 'Name of the directory the group is a member of. For example, an LDAP or Active Directory domain name.', + name: 'server.user.group.domain', + type: 'keyword', + }, + 'server.user.group.id': { + category: 'server', + description: 'Unique identifier for the group on the system/platform.', + name: 'server.user.group.id', + type: 'keyword', + }, + 'server.user.group.name': { + category: 'server', + description: 'Name of the group.', + name: 'server.user.group.name', + type: 'keyword', + }, + 'server.user.hash': { + category: 'server', + description: + 'Unique user hash to correlate information for a user in anonymized form. Useful if `user.id` or `user.name` contain confidential information and cannot be used.', + name: 'server.user.hash', + type: 'keyword', + }, + 'server.user.id': { + category: 'server', + description: 'Unique identifiers of the user.', + name: 'server.user.id', + type: 'keyword', + }, + 'server.user.name': { + category: 'server', + description: 'Short name or login of the user.', + example: 'albert', + name: 'server.user.name', + type: 'keyword', + }, + 'service.ephemeral_id': { + category: 'service', + description: + 'Ephemeral identifier of this service (if one exists). This id normally changes across restarts, but `service.id` does not.', + example: '8a4f500f', + name: 'service.ephemeral_id', + type: 'keyword', + }, + 'service.id': { + category: 'service', + description: + 'Unique identifier of the running service. If the service is comprised of many nodes, the `service.id` should be the same for all nodes. This id should uniquely identify the service. This makes it possible to correlate logs and metrics for one specific service, no matter which particular node emitted the event. Note that if you need to see the events from one specific host of the service, you should filter on that `host.name` or `host.id` instead.', + example: 'd37e5ebfe0ae6c4972dbe9f0174a1637bb8247f6', + name: 'service.id', + type: 'keyword', + }, + 'service.name': { + category: 'service', + description: + 'Name of the service data is collected from. The name of the service is normally user given. This allows for distributed services that run on multiple hosts to correlate the related instances based on the name. In the case of Elasticsearch the `service.name` could contain the cluster name. For Beats the `service.name` is by default a copy of the `service.type` field if no name is specified.', + example: 'elasticsearch-metrics', + name: 'service.name', + type: 'keyword', + }, + 'service.node.name': { + category: 'service', + description: + "Name of a service node. This allows for two nodes of the same service running on the same host to be differentiated. Therefore, `service.node.name` should typically be unique across nodes of a given service. In the case of Elasticsearch, the `service.node.name` could contain the unique node name within the Elasticsearch cluster. In cases where the service doesn't have the concept of a node name, the host name or container name can be used to distinguish running instances that make up this service. If those do not provide uniqueness (e.g. multiple instances of the service running on the same host) - the node name can be manually set.", + example: 'instance-0000000016', + name: 'service.node.name', + type: 'keyword', + }, + 'service.state': { + category: 'service', + description: 'Current state of the service.', + name: 'service.state', + type: 'keyword', + }, + 'service.type': { + category: 'service', + description: + 'The type of the service data is collected from. The type can be used to group and correlate logs and metrics from one service type. Example: If logs or metrics are collected from Elasticsearch, `service.type` would be `elasticsearch`.', + example: 'elasticsearch', + name: 'service.type', + type: 'keyword', + }, + 'service.version': { + category: 'service', + description: + 'Version of the service the data was collected from. This allows to look at a data set only for a specific version of a service.', + example: '3.2.4', + name: 'service.version', + type: 'keyword', + }, + 'source.address': { + category: 'source', + description: + 'Some event source addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the `.address` field. Then it should be duplicated to `.ip` or `.domain`, depending on which one it is.', + name: 'source.address', + type: 'keyword', + }, + 'source.as.number': { + category: 'source', + description: + 'Unique number allocated to the autonomous system. The autonomous system number (ASN) uniquely identifies each network on the Internet.', + example: 15169, + name: 'source.as.number', + type: 'long', + }, + 'source.as.organization.name': { + category: 'source', + description: 'Organization name.', + example: 'Google LLC', + name: 'source.as.organization.name', + type: 'keyword', + }, + 'source.bytes': { + category: 'source', + description: 'Bytes sent from the source to the destination.', + example: 184, + name: 'source.bytes', + type: 'long', + format: 'bytes', + }, + 'source.domain': { + category: 'source', + description: 'Source domain.', + name: 'source.domain', + type: 'keyword', + }, + 'source.geo.city_name': { + category: 'source', + description: 'City name.', + example: 'Montreal', + name: 'source.geo.city_name', + type: 'keyword', + }, + 'source.geo.continent_name': { + category: 'source', + description: 'Name of the continent.', + example: 'North America', + name: 'source.geo.continent_name', + type: 'keyword', + }, + 'source.geo.country_iso_code': { + category: 'source', + description: 'Country ISO code.', + example: 'CA', + name: 'source.geo.country_iso_code', + type: 'keyword', + }, + 'source.geo.country_name': { + category: 'source', + description: 'Country name.', + example: 'Canada', + name: 'source.geo.country_name', + type: 'keyword', + }, + 'source.geo.location': { + category: 'source', + description: 'Longitude and latitude.', + example: '{ "lon": -73.614830, "lat": 45.505918 }', + name: 'source.geo.location', + type: 'geo_point', + }, + 'source.geo.name': { + category: 'source', + description: + 'User-defined description of a location, at the level of granularity they care about. Could be the name of their data centers, the floor number, if this describes a local physical entity, city names. Not typically used in automated geolocation.', + example: 'boston-dc', + name: 'source.geo.name', + type: 'keyword', + }, + 'source.geo.region_iso_code': { + category: 'source', + description: 'Region ISO code.', + example: 'CA-QC', + name: 'source.geo.region_iso_code', + type: 'keyword', + }, + 'source.geo.region_name': { + category: 'source', + description: 'Region name.', + example: 'Quebec', + name: 'source.geo.region_name', + type: 'keyword', + }, + 'source.ip': { + category: 'source', + description: 'IP address of the source. Can be one or multiple IPv4 or IPv6 addresses.', + name: 'source.ip', + type: 'ip', + }, + 'source.mac': { + category: 'source', + description: 'MAC address of the source.', + name: 'source.mac', + type: 'keyword', + }, + 'source.nat.ip': { + category: 'source', + description: + 'Translated ip of source based NAT sessions (e.g. internal client to internet) Typically connections traversing load balancers, firewalls, or routers.', + name: 'source.nat.ip', + type: 'ip', + }, + 'source.nat.port': { + category: 'source', + description: + 'Translated port of source based NAT sessions. (e.g. internal client to internet) Typically used with load balancers, firewalls, or routers.', + name: 'source.nat.port', + type: 'long', + format: 'string', + }, + 'source.packets': { + category: 'source', + description: 'Packets sent from the source to the destination.', + example: 12, + name: 'source.packets', + type: 'long', + }, + 'source.port': { + category: 'source', + description: 'Port of the source.', + name: 'source.port', + type: 'long', + format: 'string', + }, + 'source.registered_domain': { + category: 'source', + description: + 'The highest registered source domain, stripped of the subdomain. For example, the registered domain for "foo.google.com" is "google.com". This value can be determined precisely with a list like the public suffix list (http://publicsuffix.org). Trying to approximate this by simply taking the last two labels will not work well for TLDs such as "co.uk".', + example: 'google.com', + name: 'source.registered_domain', + type: 'keyword', + }, + 'source.top_level_domain': { + category: 'source', + description: + 'The effective top level domain (eTLD), also known as the domain suffix, is the last part of the domain name. For example, the top level domain for google.com is "com". This value can be determined precisely with a list like the public suffix list (http://publicsuffix.org). Trying to approximate this by simply taking the last label will not work well for effective TLDs such as "co.uk".', + example: 'co.uk', + name: 'source.top_level_domain', + type: 'keyword', + }, + 'source.user.domain': { + category: 'source', + description: + 'Name of the directory the user is a member of. For example, an LDAP or Active Directory domain name.', + name: 'source.user.domain', + type: 'keyword', + }, + 'source.user.email': { + category: 'source', + description: 'User email address.', + name: 'source.user.email', + type: 'keyword', + }, + 'source.user.full_name': { + category: 'source', + description: "User's full name, if available.", + example: 'Albert Einstein', + name: 'source.user.full_name', + type: 'keyword', + }, + 'source.user.group.domain': { + category: 'source', + description: + 'Name of the directory the group is a member of. For example, an LDAP or Active Directory domain name.', + name: 'source.user.group.domain', + type: 'keyword', + }, + 'source.user.group.id': { + category: 'source', + description: 'Unique identifier for the group on the system/platform.', + name: 'source.user.group.id', + type: 'keyword', + }, + 'source.user.group.name': { + category: 'source', + description: 'Name of the group.', + name: 'source.user.group.name', + type: 'keyword', + }, + 'source.user.hash': { + category: 'source', + description: + 'Unique user hash to correlate information for a user in anonymized form. Useful if `user.id` or `user.name` contain confidential information and cannot be used.', + name: 'source.user.hash', + type: 'keyword', + }, + 'source.user.id': { + category: 'source', + description: 'Unique identifiers of the user.', + name: 'source.user.id', + type: 'keyword', + }, + 'source.user.name': { + category: 'source', + description: 'Short name or login of the user.', + example: 'albert', + name: 'source.user.name', + type: 'keyword', + }, + 'threat.framework': { + category: 'threat', + description: + 'Name of the threat framework used to further categorize and classify the tactic and technique of the reported threat. Framework classification can be provided by detecting systems, evaluated at ingest time, or retrospectively tagged to events.', + example: 'MITRE ATT&CK', + name: 'threat.framework', + type: 'keyword', + }, + 'threat.tactic.id': { + category: 'threat', + description: + 'The id of tactic used by this threat. You can use the Mitre ATT&CK Matrix Tactic categorization, for example. (ex. https://attack.mitre.org/tactics/TA0040/ )', + example: 'TA0040', + name: 'threat.tactic.id', + type: 'keyword', + }, + 'threat.tactic.name': { + category: 'threat', + description: + 'Name of the type of tactic used by this threat. You can use the Mitre ATT&CK Matrix Tactic categorization, for example. (ex. https://attack.mitre.org/tactics/TA0040/ )', + example: 'impact', + name: 'threat.tactic.name', + type: 'keyword', + }, + 'threat.tactic.reference': { + category: 'threat', + description: + 'The reference url of tactic used by this threat. You can use the Mitre ATT&CK Matrix Tactic categorization, for example. (ex. https://attack.mitre.org/tactics/TA0040/ )', + example: 'https://attack.mitre.org/tactics/TA0040/', + name: 'threat.tactic.reference', + type: 'keyword', + }, + 'threat.technique.id': { + category: 'threat', + description: + 'The id of technique used by this tactic. You can use the Mitre ATT&CK Matrix Tactic categorization, for example. (ex. https://attack.mitre.org/techniques/T1499/ )', + example: 'T1499', + name: 'threat.technique.id', + type: 'keyword', + }, + 'threat.technique.name': { + category: 'threat', + description: + 'The name of technique used by this tactic. You can use the Mitre ATT&CK Matrix Tactic categorization, for example. (ex. https://attack.mitre.org/techniques/T1499/ )', + example: 'endpoint denial of service', + name: 'threat.technique.name', + type: 'keyword', + }, + 'threat.technique.reference': { + category: 'threat', + description: + 'The reference url of technique used by this tactic. You can use the Mitre ATT&CK Matrix Tactic categorization, for example. (ex. https://attack.mitre.org/techniques/T1499/ )', + example: 'https://attack.mitre.org/techniques/T1499/', + name: 'threat.technique.reference', + type: 'keyword', + }, + 'tls.cipher': { + category: 'tls', + description: 'String indicating the cipher used during the current connection.', + example: 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256', + name: 'tls.cipher', + type: 'keyword', + }, + 'tls.client.certificate': { + category: 'tls', + description: + 'PEM-encoded stand-alone certificate offered by the client. This is usually mutually-exclusive of `client.certificate_chain` since this value also exists in that list.', + example: 'MII...', + name: 'tls.client.certificate', + type: 'keyword', + }, + 'tls.client.certificate_chain': { + category: 'tls', + description: + 'Array of PEM-encoded certificates that make up the certificate chain offered by the client. This is usually mutually-exclusive of `client.certificate` since that value should be the first certificate in the chain.', + example: '["MII...","MII..."]', + name: 'tls.client.certificate_chain', + type: 'keyword', + }, + 'tls.client.hash.md5': { + category: 'tls', + description: + 'Certificate fingerprint using the MD5 digest of DER-encoded version of certificate offered by the client. For consistency with other hash values, this value should be formatted as an uppercase hash.', + example: '0F76C7F2C55BFD7D8E8B8F4BFBF0C9EC', + name: 'tls.client.hash.md5', + type: 'keyword', + }, + 'tls.client.hash.sha1': { + category: 'tls', + description: + 'Certificate fingerprint using the SHA1 digest of DER-encoded version of certificate offered by the client. For consistency with other hash values, this value should be formatted as an uppercase hash.', + example: '9E393D93138888D288266C2D915214D1D1CCEB2A', + name: 'tls.client.hash.sha1', + type: 'keyword', + }, + 'tls.client.hash.sha256': { + category: 'tls', + description: + 'Certificate fingerprint using the SHA256 digest of DER-encoded version of certificate offered by the client. For consistency with other hash values, this value should be formatted as an uppercase hash.', + example: '0687F666A054EF17A08E2F2162EAB4CBC0D265E1D7875BE74BF3C712CA92DAF0', + name: 'tls.client.hash.sha256', + type: 'keyword', + }, + 'tls.client.issuer': { + category: 'tls', + description: + 'Distinguished name of subject of the issuer of the x.509 certificate presented by the client.', + example: 'CN=MyDomain Root CA, OU=Infrastructure Team, DC=mydomain, DC=com', + name: 'tls.client.issuer', + type: 'keyword', + }, + 'tls.client.ja3': { + category: 'tls', + description: 'A hash that identifies clients based on how they perform an SSL/TLS handshake.', + example: 'd4e5b18d6b55c71272893221c96ba240', + name: 'tls.client.ja3', + type: 'keyword', + }, + 'tls.client.not_after': { + category: 'tls', + description: 'Date/Time indicating when client certificate is no longer considered valid.', + example: '2021-01-01T00:00:00.000Z', + name: 'tls.client.not_after', + type: 'date', + }, + 'tls.client.not_before': { + category: 'tls', + description: 'Date/Time indicating when client certificate is first considered valid.', + example: '1970-01-01T00:00:00.000Z', + name: 'tls.client.not_before', + type: 'date', + }, + 'tls.client.server_name': { + category: 'tls', + description: + 'Also called an SNI, this tells the server which hostname to which the client is attempting to connect. When this value is available, it should get copied to `destination.domain`.', + example: 'www.elastic.co', + name: 'tls.client.server_name', + type: 'keyword', + }, + 'tls.client.subject': { + category: 'tls', + description: 'Distinguished name of subject of the x.509 certificate presented by the client.', + example: 'CN=myclient, OU=Documentation Team, DC=mydomain, DC=com', + name: 'tls.client.subject', + type: 'keyword', + }, + 'tls.client.supported_ciphers': { + category: 'tls', + description: 'Array of ciphers offered by the client during the client hello.', + example: + '["TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384","..."]', + name: 'tls.client.supported_ciphers', + type: 'keyword', + }, + 'tls.curve': { + category: 'tls', + description: 'String indicating the curve used for the given cipher, when applicable.', + example: 'secp256r1', + name: 'tls.curve', + type: 'keyword', + }, + 'tls.established': { + category: 'tls', + description: + 'Boolean flag indicating if the TLS negotiation was successful and transitioned to an encrypted tunnel.', + name: 'tls.established', + type: 'boolean', + }, + 'tls.next_protocol': { + category: 'tls', + description: + 'String indicating the protocol being tunneled. Per the values in the IANA registry (https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids), this string should be lower case.', + example: 'http/1.1', + name: 'tls.next_protocol', + type: 'keyword', + }, + 'tls.resumed': { + category: 'tls', + description: + 'Boolean flag indicating if this TLS connection was resumed from an existing TLS negotiation.', + name: 'tls.resumed', + type: 'boolean', + }, + 'tls.server.certificate': { + category: 'tls', + description: + 'PEM-encoded stand-alone certificate offered by the server. This is usually mutually-exclusive of `server.certificate_chain` since this value also exists in that list.', + example: 'MII...', + name: 'tls.server.certificate', + type: 'keyword', + }, + 'tls.server.certificate_chain': { + category: 'tls', + description: + 'Array of PEM-encoded certificates that make up the certificate chain offered by the server. This is usually mutually-exclusive of `server.certificate` since that value should be the first certificate in the chain.', + example: '["MII...","MII..."]', + name: 'tls.server.certificate_chain', + type: 'keyword', + }, + 'tls.server.hash.md5': { + category: 'tls', + description: + 'Certificate fingerprint using the MD5 digest of DER-encoded version of certificate offered by the server. For consistency with other hash values, this value should be formatted as an uppercase hash.', + example: '0F76C7F2C55BFD7D8E8B8F4BFBF0C9EC', + name: 'tls.server.hash.md5', + type: 'keyword', + }, + 'tls.server.hash.sha1': { + category: 'tls', + description: + 'Certificate fingerprint using the SHA1 digest of DER-encoded version of certificate offered by the server. For consistency with other hash values, this value should be formatted as an uppercase hash.', + example: '9E393D93138888D288266C2D915214D1D1CCEB2A', + name: 'tls.server.hash.sha1', + type: 'keyword', + }, + 'tls.server.hash.sha256': { + category: 'tls', + description: + 'Certificate fingerprint using the SHA256 digest of DER-encoded version of certificate offered by the server. For consistency with other hash values, this value should be formatted as an uppercase hash.', + example: '0687F666A054EF17A08E2F2162EAB4CBC0D265E1D7875BE74BF3C712CA92DAF0', + name: 'tls.server.hash.sha256', + type: 'keyword', + }, + 'tls.server.issuer': { + category: 'tls', + description: 'Subject of the issuer of the x.509 certificate presented by the server.', + example: 'CN=MyDomain Root CA, OU=Infrastructure Team, DC=mydomain, DC=com', + name: 'tls.server.issuer', + type: 'keyword', + }, + 'tls.server.ja3s': { + category: 'tls', + description: 'A hash that identifies servers based on how they perform an SSL/TLS handshake.', + example: '394441ab65754e2207b1e1b457b3641d', + name: 'tls.server.ja3s', + type: 'keyword', + }, + 'tls.server.not_after': { + category: 'tls', + description: 'Timestamp indicating when server certificate is no longer considered valid.', + example: '2021-01-01T00:00:00.000Z', + name: 'tls.server.not_after', + type: 'date', + }, + 'tls.server.not_before': { + category: 'tls', + description: 'Timestamp indicating when server certificate is first considered valid.', + example: '1970-01-01T00:00:00.000Z', + name: 'tls.server.not_before', + type: 'date', + }, + 'tls.server.subject': { + category: 'tls', + description: 'Subject of the x.509 certificate presented by the server.', + example: 'CN=www.mydomain.com, OU=Infrastructure Team, DC=mydomain, DC=com', + name: 'tls.server.subject', + type: 'keyword', + }, + 'tls.version': { + category: 'tls', + description: 'Numeric part of the version parsed from the original string.', + example: '1.2', + name: 'tls.version', + type: 'keyword', + }, + 'tls.version_protocol': { + category: 'tls', + description: 'Normalized lowercase protocol name parsed from original string.', + example: 'tls', + name: 'tls.version_protocol', + type: 'keyword', + }, + 'tracing.trace.id': { + category: 'tracing', + description: + 'Unique identifier of the trace. A trace groups multiple events like transactions that belong together. For example, a user request handled by multiple inter-connected services.', + example: '4bf92f3577b34da6a3ce929d0e0e4736', + name: 'tracing.trace.id', + type: 'keyword', + }, + 'tracing.transaction.id': { + category: 'tracing', + description: + 'Unique identifier of the transaction. A transaction is the highest level of work measured within a service, such as a request to a server.', + example: '00f067aa0ba902b7', + name: 'tracing.transaction.id', + type: 'keyword', + }, + 'url.domain': { + category: 'url', + description: + 'Domain of the url, such as "www.elastic.co". In some cases a URL may refer to an IP and/or port directly, without a domain name. In this case, the IP address would go to the `domain` field.', + example: 'www.elastic.co', + name: 'url.domain', + type: 'keyword', + }, + 'url.extension': { + category: 'url', + description: + 'The field contains the file extension from the original request url. The file extension is only set if it exists, as not every url has a file extension. The leading period must not be included. For example, the value must be "png", not ".png".', + example: 'png', + name: 'url.extension', + type: 'keyword', + }, + 'url.fragment': { + category: 'url', + description: + 'Portion of the url after the `#`, such as "top". The `#` is not part of the fragment.', + name: 'url.fragment', + type: 'keyword', + }, + 'url.full': { + category: 'url', + description: + 'If full URLs are important to your use case, they should be stored in `url.full`, whether this field is reconstructed or present in the event source.', + example: 'https://www.elastic.co:443/search?q=elasticsearch#top', + name: 'url.full', + type: 'keyword', + }, + 'url.original': { + category: 'url', + description: + 'Unmodified original url as seen in the event source. Note that in network monitoring, the observed URL may be a full URL, whereas in access logs, the URL is often just represented as a path. This field is meant to represent the URL as it was observed, complete or not.', + example: 'https://www.elastic.co:443/search?q=elasticsearch#top or /search?q=elasticsearch', + name: 'url.original', + type: 'keyword', + }, + 'url.password': { + category: 'url', + description: 'Password of the request.', + name: 'url.password', + type: 'keyword', + }, + 'url.path': { + category: 'url', + description: 'Path of the request, such as "/search".', + name: 'url.path', + type: 'keyword', + }, + 'url.port': { + category: 'url', + description: 'Port of the request, such as 443.', + example: 443, + name: 'url.port', + type: 'long', + format: 'string', + }, + 'url.query': { + category: 'url', + description: + 'The query field describes the query string of the request, such as "q=elasticsearch". The `?` is excluded from the query string. If a URL contains no `?`, there is no query field. If there is a `?` but no query, the query field exists with an empty string. The `exists` query can be used to differentiate between the two cases.', + name: 'url.query', + type: 'keyword', + }, + 'url.registered_domain': { + category: 'url', + description: + 'The highest registered url domain, stripped of the subdomain. For example, the registered domain for "foo.google.com" is "google.com". This value can be determined precisely with a list like the public suffix list (http://publicsuffix.org). Trying to approximate this by simply taking the last two labels will not work well for TLDs such as "co.uk".', + example: 'google.com', + name: 'url.registered_domain', + type: 'keyword', + }, + 'url.scheme': { + category: 'url', + description: 'Scheme of the request, such as "https". Note: The `:` is not part of the scheme.', + example: 'https', + name: 'url.scheme', + type: 'keyword', + }, + 'url.top_level_domain': { + category: 'url', + description: + 'The effective top level domain (eTLD), also known as the domain suffix, is the last part of the domain name. For example, the top level domain for google.com is "com". This value can be determined precisely with a list like the public suffix list (http://publicsuffix.org). Trying to approximate this by simply taking the last label will not work well for effective TLDs such as "co.uk".', + example: 'co.uk', + name: 'url.top_level_domain', + type: 'keyword', + }, + 'url.username': { + category: 'url', + description: 'Username of the request.', + name: 'url.username', + type: 'keyword', + }, + 'user.domain': { + category: 'user', + description: + 'Name of the directory the user is a member of. For example, an LDAP or Active Directory domain name.', + name: 'user.domain', + type: 'keyword', + }, + 'user.email': { + category: 'user', + description: 'User email address.', + name: 'user.email', + type: 'keyword', + }, + 'user.full_name': { + category: 'user', + description: "User's full name, if available.", + example: 'Albert Einstein', + name: 'user.full_name', + type: 'keyword', + }, + 'user.group.domain': { + category: 'user', + description: + 'Name of the directory the group is a member of. For example, an LDAP or Active Directory domain name.', + name: 'user.group.domain', + type: 'keyword', + }, + 'user.group.id': { + category: 'user', + description: 'Unique identifier for the group on the system/platform.', + name: 'user.group.id', + type: 'keyword', + }, + 'user.group.name': { + category: 'user', + description: 'Name of the group.', + name: 'user.group.name', + type: 'keyword', + }, + 'user.hash': { + category: 'user', + description: + 'Unique user hash to correlate information for a user in anonymized form. Useful if `user.id` or `user.name` contain confidential information and cannot be used.', + name: 'user.hash', + type: 'keyword', + }, + 'user.id': { + category: 'user', + description: 'Unique identifiers of the user.', + name: 'user.id', + type: 'keyword', + }, + 'user.name': { + category: 'user', + description: 'Short name or login of the user.', + example: 'albert', + name: 'user.name', + type: 'keyword', + }, + 'user_agent.device.name': { + category: 'user_agent', + description: 'Name of the device.', + example: 'iPhone', + name: 'user_agent.device.name', + type: 'keyword', + }, + 'user_agent.name': { + category: 'user_agent', + description: 'Name of the user agent.', + example: 'Safari', + name: 'user_agent.name', + type: 'keyword', + }, + 'user_agent.original': { + category: 'user_agent', + description: 'Unparsed user_agent string.', + example: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1', + name: 'user_agent.original', + type: 'keyword', + }, + 'user_agent.os.family': { + category: 'user_agent', + description: 'OS family (such as redhat, debian, freebsd, windows).', + example: 'debian', + name: 'user_agent.os.family', + type: 'keyword', + }, + 'user_agent.os.full': { + category: 'user_agent', + description: 'Operating system name, including the version or code name.', + example: 'Mac OS Mojave', + name: 'user_agent.os.full', + type: 'keyword', + }, + 'user_agent.os.kernel': { + category: 'user_agent', + description: 'Operating system kernel version as a raw string.', + example: '4.4.0-112-generic', + name: 'user_agent.os.kernel', + type: 'keyword', + }, + 'user_agent.os.name': { + category: 'user_agent', + description: 'Operating system name, without the version.', + example: 'Mac OS X', + name: 'user_agent.os.name', + type: 'keyword', + }, + 'user_agent.os.platform': { + category: 'user_agent', + description: 'Operating system platform (such centos, ubuntu, windows).', + example: 'darwin', + name: 'user_agent.os.platform', + type: 'keyword', + }, + 'user_agent.os.version': { + category: 'user_agent', + description: 'Operating system version as a raw string.', + example: '10.14.1', + name: 'user_agent.os.version', + type: 'keyword', + }, + 'user_agent.version': { + category: 'user_agent', + description: 'Version of the user agent.', + example: 12, + name: 'user_agent.version', + type: 'keyword', + }, + 'vlan.id': { + category: 'vlan', + description: 'VLAN ID as reported by the observer.', + example: 10, + name: 'vlan.id', + type: 'keyword', + }, + 'vlan.name': { + category: 'vlan', + description: 'Optional VLAN name as reported by the observer.', + example: 'outside', + name: 'vlan.name', + type: 'keyword', + }, + 'vulnerability.category': { + category: 'vulnerability', + description: + 'The type of system or architecture that the vulnerability affects. These may be platform-specific (for example, Debian or SUSE) or general (for example, Database or Firewall). For example (https://qualysguard.qualys.com/qwebhelp/fo_portal/knowledgebase/vulnerability_categories.htm[Qualys vulnerability categories]) This field must be an array.', + example: '["Firewall"]', + name: 'vulnerability.category', + type: 'keyword', + }, + 'vulnerability.classification': { + category: 'vulnerability', + description: + 'The classification of the vulnerability scoring system. For example (https://www.first.org/cvss/)', + example: 'CVSS', + name: 'vulnerability.classification', + type: 'keyword', + }, + 'vulnerability.description': { + category: 'vulnerability', + description: + 'The description of the vulnerability that provides additional context of the vulnerability. For example (https://cve.mitre.org/about/faqs.html#cve_entry_descriptions_created[Common Vulnerabilities and Exposure CVE description])', + example: 'In macOS before 2.12.6, there is a vulnerability in the RPC...', + name: 'vulnerability.description', + type: 'keyword', + }, + 'vulnerability.enumeration': { + category: 'vulnerability', + description: + 'The type of identifier used for this vulnerability. For example (https://cve.mitre.org/about/)', + example: 'CVE', + name: 'vulnerability.enumeration', + type: 'keyword', + }, + 'vulnerability.id': { + category: 'vulnerability', + description: + 'The identification (ID) is the number portion of a vulnerability entry. It includes a unique identification number for the vulnerability. For example (https://cve.mitre.org/about/faqs.html#what_is_cve_id)[Common Vulnerabilities and Exposure CVE ID]', + example: 'CVE-2019-00001', + name: 'vulnerability.id', + type: 'keyword', + }, + 'vulnerability.reference': { + category: 'vulnerability', + description: + 'A resource that provides additional information, context, and mitigations for the identified vulnerability.', + example: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-6111', + name: 'vulnerability.reference', + type: 'keyword', + }, + 'vulnerability.report_id': { + category: 'vulnerability', + description: 'The report or scan identification number.', + example: 20191018.0001, + name: 'vulnerability.report_id', + type: 'keyword', + }, + 'vulnerability.scanner.vendor': { + category: 'vulnerability', + description: 'The name of the vulnerability scanner vendor.', + example: 'Tenable', + name: 'vulnerability.scanner.vendor', + type: 'keyword', + }, + 'vulnerability.score.base': { + category: 'vulnerability', + description: + 'Scores can range from 0.0 to 10.0, with 10.0 being the most severe. Base scores cover an assessment for exploitability metrics (attack vector, complexity, privileges, and user interaction), impact metrics (confidentiality, integrity, and availability), and scope. For example (https://www.first.org/cvss/specification-document)', + example: 5.5, + name: 'vulnerability.score.base', + type: 'float', + }, + 'vulnerability.score.environmental': { + category: 'vulnerability', + description: + 'Scores can range from 0.0 to 10.0, with 10.0 being the most severe. Environmental scores cover an assessment for any modified Base metrics, confidentiality, integrity, and availability requirements. For example (https://www.first.org/cvss/specification-document)', + example: 5.5, + name: 'vulnerability.score.environmental', + type: 'float', + }, + 'vulnerability.score.temporal': { + category: 'vulnerability', + description: + 'Scores can range from 0.0 to 10.0, with 10.0 being the most severe. Temporal scores cover an assessment for code maturity, remediation level, and confidence. For example (https://www.first.org/cvss/specification-document)', + name: 'vulnerability.score.temporal', + type: 'float', + }, + 'vulnerability.score.version': { + category: 'vulnerability', + description: + 'The National Vulnerability Database (NVD) provides qualitative severity rankings of "Low", "Medium", and "High" for CVSS v2.0 base score ranges in addition to the severity ratings for CVSS v3.0 as they are defined in the CVSS v3.0 specification. CVSS is owned and managed by FIRST.Org, Inc. (FIRST), a US-based non-profit organization, whose mission is to help computer security incident response teams across the world. For example (https://nvd.nist.gov/vuln-metrics/cvss)', + example: 2, + name: 'vulnerability.score.version', + type: 'keyword', + }, + 'vulnerability.severity': { + category: 'vulnerability', + description: + 'The severity of the vulnerability can help with metrics and internal prioritization regarding remediation. For example (https://nvd.nist.gov/vuln-metrics/cvss)', + example: 'Critical', + name: 'vulnerability.severity', + type: 'keyword', + }, + 'agent.hostname': { + category: 'agent', + description: + 'Deprecated - use agent.name or agent.id to identify an agent. Hostname of the agent. ', + name: 'agent.hostname', + type: 'keyword', + }, + 'beat.timezone': { + category: 'beat', + name: 'beat.timezone', + type: 'alias', + }, + fields: { + category: 'base', + description: 'Contains user configurable fields. ', + name: 'fields', + type: 'object', + }, + 'beat.name': { + category: 'beat', + name: 'beat.name', + type: 'alias', + }, + 'beat.hostname': { + category: 'beat', + name: 'beat.hostname', + type: 'alias', + }, + 'timeseries.instance': { + category: 'timeseries', + description: 'Time series instance id', + name: 'timeseries.instance', + type: 'keyword', + }, + 'cloud.project.id': { + category: 'cloud', + description: 'Name of the project in Google Cloud. ', + example: 'project-x', + name: 'cloud.project.id', + }, + 'cloud.image.id': { + category: 'cloud', + description: 'Image ID for the cloud instance. ', + example: 'ami-abcd1234', + name: 'cloud.image.id', + }, + 'meta.cloud.provider': { + category: 'meta', + name: 'meta.cloud.provider', + type: 'alias', + }, + 'meta.cloud.instance_id': { + category: 'meta', + name: 'meta.cloud.instance_id', + type: 'alias', + }, + 'meta.cloud.instance_name': { + category: 'meta', + name: 'meta.cloud.instance_name', + type: 'alias', + }, + 'meta.cloud.machine_type': { + category: 'meta', + name: 'meta.cloud.machine_type', + type: 'alias', + }, + 'meta.cloud.availability_zone': { + category: 'meta', + name: 'meta.cloud.availability_zone', + type: 'alias', + }, + 'meta.cloud.project_id': { + category: 'meta', + name: 'meta.cloud.project_id', + type: 'alias', + }, + 'meta.cloud.region': { + category: 'meta', + name: 'meta.cloud.region', + type: 'alias', + }, + 'docker.container.id': { + category: 'docker', + name: 'docker.container.id', + type: 'alias', + }, + 'docker.container.image': { + category: 'docker', + name: 'docker.container.image', + type: 'alias', + }, + 'docker.container.name': { + category: 'docker', + name: 'docker.container.name', + type: 'alias', + }, + 'docker.container.labels': { + category: 'docker', + description: 'Image labels. ', + name: 'docker.container.labels', + type: 'object', + }, + 'host.containerized': { + category: 'host', + description: 'If the host is a container. ', + name: 'host.containerized', + type: 'boolean', + }, + 'host.os.build': { + category: 'host', + description: 'OS build information. ', + example: '18D109', + name: 'host.os.build', + type: 'keyword', + }, + 'host.os.codename': { + category: 'host', + description: 'OS codename, if any. ', + example: 'stretch', + name: 'host.os.codename', + type: 'keyword', + }, + 'kubernetes.pod.name': { + category: 'kubernetes', + description: 'Kubernetes pod name ', + name: 'kubernetes.pod.name', + type: 'keyword', + }, + 'kubernetes.pod.uid': { + category: 'kubernetes', + description: 'Kubernetes Pod UID ', + name: 'kubernetes.pod.uid', + type: 'keyword', + }, + 'kubernetes.namespace': { + category: 'kubernetes', + description: 'Kubernetes namespace ', + name: 'kubernetes.namespace', + type: 'keyword', + }, + 'kubernetes.node.name': { + category: 'kubernetes', + description: 'Kubernetes node name ', + name: 'kubernetes.node.name', + type: 'keyword', + }, + 'kubernetes.labels.*': { + category: 'kubernetes', + description: 'Kubernetes labels map ', + name: 'kubernetes.labels.*', + type: 'object', + }, + 'kubernetes.annotations.*': { + category: 'kubernetes', + description: 'Kubernetes annotations map ', + name: 'kubernetes.annotations.*', + type: 'object', + }, + 'kubernetes.replicaset.name': { + category: 'kubernetes', + description: 'Kubernetes replicaset name ', + name: 'kubernetes.replicaset.name', + type: 'keyword', + }, + 'kubernetes.deployment.name': { + category: 'kubernetes', + description: 'Kubernetes deployment name ', + name: 'kubernetes.deployment.name', + type: 'keyword', + }, + 'kubernetes.statefulset.name': { + category: 'kubernetes', + description: 'Kubernetes statefulset name ', + name: 'kubernetes.statefulset.name', + type: 'keyword', + }, + 'kubernetes.container.name': { + category: 'kubernetes', + description: 'Kubernetes container name ', + name: 'kubernetes.container.name', + type: 'keyword', + }, + 'kubernetes.container.image': { + category: 'kubernetes', + description: 'Kubernetes container image ', + name: 'kubernetes.container.image', + type: 'keyword', + }, + 'process.exe': { + category: 'process', + name: 'process.exe', + type: 'alias', + }, + 'jolokia.agent.version': { + category: 'jolokia', + description: 'Version number of jolokia agent. ', + name: 'jolokia.agent.version', + type: 'keyword', + }, + 'jolokia.agent.id': { + category: 'jolokia', + description: + 'Each agent has a unique id which can be either provided during startup of the agent in form of a configuration parameter or being autodetected. If autodected, the id has several parts: The IP, the process id, hashcode of the agent and its type. ', + name: 'jolokia.agent.id', + type: 'keyword', + }, + 'jolokia.server.product': { + category: 'jolokia', + description: 'The container product if detected. ', + name: 'jolokia.server.product', + type: 'keyword', + }, + 'jolokia.server.version': { + category: 'jolokia', + description: "The container's version (if detected). ", + name: 'jolokia.server.version', + type: 'keyword', + }, + 'jolokia.server.vendor': { + category: 'jolokia', + description: 'The vendor of the container the agent is running in. ', + name: 'jolokia.server.vendor', + type: 'keyword', + }, + 'jolokia.url': { + category: 'jolokia', + description: 'The URL how this agent can be contacted. ', + name: 'jolokia.url', + type: 'keyword', + }, + 'jolokia.secured': { + category: 'jolokia', + description: 'Whether the agent was configured for authentication or not. ', + name: 'jolokia.secured', + type: 'boolean', + }, + 'file.setuid': { + category: 'file', + description: 'Set if the file has the `setuid` bit set. Omitted otherwise.', + example: 'true', + name: 'file.setuid', + type: 'boolean', + }, + 'file.setgid': { + category: 'file', + description: 'Set if the file has the `setgid` bit set. Omitted otherwise.', + example: 'true', + name: 'file.setgid', + type: 'boolean', + }, + 'file.origin': { + category: 'file', + description: + 'An array of strings describing a possible external origin for this file. For example, the URL it was downloaded from. Only supported in macOS, via the kMDItemWhereFroms attribute. Omitted if origin information is not available. ', + name: 'file.origin', + type: 'keyword', + }, + 'file.selinux.user': { + category: 'file', + description: 'The owner of the object.', + name: 'file.selinux.user', + type: 'keyword', + }, + 'file.selinux.role': { + category: 'file', + description: "The object's SELinux role.", + name: 'file.selinux.role', + type: 'keyword', + }, + 'file.selinux.domain': { + category: 'file', + description: "The object's SELinux domain or type.", + name: 'file.selinux.domain', + type: 'keyword', + }, + 'file.selinux.level': { + category: 'file', + description: "The object's SELinux level.", + example: 's0', + name: 'file.selinux.level', + type: 'keyword', + }, + 'user.audit.id': { + category: 'user', + description: 'Audit user ID.', + name: 'user.audit.id', + type: 'keyword', + }, + 'user.audit.name': { + category: 'user', + description: 'Audit user name.', + name: 'user.audit.name', + type: 'keyword', + }, + 'user.effective.id': { + category: 'user', + description: 'Effective user ID.', + name: 'user.effective.id', + type: 'keyword', + }, + 'user.effective.name': { + category: 'user', + description: 'Effective user name.', + name: 'user.effective.name', + type: 'keyword', + }, + 'user.effective.group.id': { + category: 'user', + description: 'Effective group ID.', + name: 'user.effective.group.id', + type: 'keyword', + }, + 'user.effective.group.name': { + category: 'user', + description: 'Effective group name.', + name: 'user.effective.group.name', + type: 'keyword', + }, + 'user.filesystem.id': { + category: 'user', + description: 'Filesystem user ID.', + name: 'user.filesystem.id', + type: 'keyword', + }, + 'user.filesystem.name': { + category: 'user', + description: 'Filesystem user name.', + name: 'user.filesystem.name', + type: 'keyword', + }, + 'user.filesystem.group.id': { + category: 'user', + description: 'Filesystem group ID.', + name: 'user.filesystem.group.id', + type: 'keyword', + }, + 'user.filesystem.group.name': { + category: 'user', + description: 'Filesystem group name.', + name: 'user.filesystem.group.name', + type: 'keyword', + }, + 'user.saved.id': { + category: 'user', + description: 'Saved user ID.', + name: 'user.saved.id', + type: 'keyword', + }, + 'user.saved.name': { + category: 'user', + description: 'Saved user name.', + name: 'user.saved.name', + type: 'keyword', + }, + 'user.saved.group.id': { + category: 'user', + description: 'Saved group ID.', + name: 'user.saved.group.id', + type: 'keyword', + }, + 'user.saved.group.name': { + category: 'user', + description: 'Saved group name.', + name: 'user.saved.group.name', + type: 'keyword', + }, + 'user.auid': { + category: 'user', + name: 'user.auid', + type: 'alias', + }, + 'user.uid': { + category: 'user', + name: 'user.uid', + type: 'alias', + }, + 'user.euid': { + category: 'user', + name: 'user.euid', + type: 'alias', + }, + 'user.fsuid': { + category: 'user', + name: 'user.fsuid', + type: 'alias', + }, + 'user.suid': { + category: 'user', + name: 'user.suid', + type: 'alias', + }, + 'user.gid': { + category: 'user', + name: 'user.gid', + type: 'alias', + }, + 'user.egid': { + category: 'user', + name: 'user.egid', + type: 'alias', + }, + 'user.sgid': { + category: 'user', + name: 'user.sgid', + type: 'alias', + }, + 'user.fsgid': { + category: 'user', + name: 'user.fsgid', + type: 'alias', + }, + 'user.name_map.auid': { + category: 'user', + name: 'user.name_map.auid', + type: 'alias', + }, + 'user.name_map.uid': { + category: 'user', + name: 'user.name_map.uid', + type: 'alias', + }, + 'user.name_map.euid': { + category: 'user', + name: 'user.name_map.euid', + type: 'alias', + }, + 'user.name_map.fsuid': { + category: 'user', + name: 'user.name_map.fsuid', + type: 'alias', + }, + 'user.name_map.suid': { + category: 'user', + name: 'user.name_map.suid', + type: 'alias', + }, + 'user.name_map.gid': { + category: 'user', + name: 'user.name_map.gid', + type: 'alias', + }, + 'user.name_map.egid': { + category: 'user', + name: 'user.name_map.egid', + type: 'alias', + }, + 'user.name_map.sgid': { + category: 'user', + name: 'user.name_map.sgid', + type: 'alias', + }, + 'user.name_map.fsgid': { + category: 'user', + name: 'user.name_map.fsgid', + type: 'alias', + }, + 'user.selinux.user': { + category: 'user', + description: 'account submitted for authentication', + name: 'user.selinux.user', + type: 'keyword', + }, + 'user.selinux.role': { + category: 'user', + description: "user's SELinux role", + name: 'user.selinux.role', + type: 'keyword', + }, + 'user.selinux.domain': { + category: 'user', + description: "The actor's SELinux domain or type.", + name: 'user.selinux.domain', + type: 'keyword', + }, + 'user.selinux.level': { + category: 'user', + description: "The actor's SELinux level.", + example: 's0', + name: 'user.selinux.level', + type: 'keyword', + }, + 'user.selinux.category': { + category: 'user', + description: "The actor's SELinux category or compartments.", + name: 'user.selinux.category', + type: 'keyword', + }, + 'process.cwd': { + category: 'process', + description: 'The current working directory.', + name: 'process.cwd', + type: 'alias', + }, + 'source.path': { + category: 'source', + description: 'This is the path associated with a unix socket.', + name: 'source.path', + type: 'keyword', + }, + 'destination.path': { + category: 'destination', + description: 'This is the path associated with a unix socket.', + name: 'destination.path', + type: 'keyword', + }, + 'auditd.message_type': { + category: 'auditd', + description: 'The audit message type (e.g. syscall or apparmor_denied). ', + example: 'syscall', + name: 'auditd.message_type', + type: 'keyword', + }, + 'auditd.sequence': { + category: 'auditd', + description: + 'The sequence number of the event as assigned by the kernel. Sequence numbers are stored as a uint32 in the kernel and can rollover. ', + name: 'auditd.sequence', + type: 'long', + }, + 'auditd.session': { + category: 'auditd', + description: + 'The session ID assigned to a login. All events related to a login session will have the same value. ', + name: 'auditd.session', + type: 'keyword', + }, + 'auditd.result': { + category: 'auditd', + description: 'The result of the audited operation (success/fail).', + example: 'success or fail', + name: 'auditd.result', + type: 'keyword', + }, + 'auditd.summary.actor.primary': { + category: 'auditd', + description: + "The primary identity of the actor. This is the actor's original login ID. It will not change even if the user changes to another account. ", + name: 'auditd.summary.actor.primary', + type: 'keyword', + }, + 'auditd.summary.actor.secondary': { + category: 'auditd', + description: + 'The secondary identity of the actor. This is typically the same as the primary, except for when the user has used `su`.', + name: 'auditd.summary.actor.secondary', + type: 'keyword', + }, + 'auditd.summary.object.type': { + category: 'auditd', + description: 'A description of the what the "thing" is (e.g. file, socket, user-session). ', + name: 'auditd.summary.object.type', + type: 'keyword', + }, + 'auditd.summary.object.primary': { + category: 'auditd', + description: '', + name: 'auditd.summary.object.primary', + type: 'keyword', + }, + 'auditd.summary.object.secondary': { + category: 'auditd', + description: '', + name: 'auditd.summary.object.secondary', + type: 'keyword', + }, + 'auditd.summary.how': { + category: 'auditd', + description: + 'This describes how the action was performed. Usually this is the exe or command that was being executed that triggered the event. ', + name: 'auditd.summary.how', + type: 'keyword', + }, + 'auditd.paths.inode': { + category: 'auditd', + description: 'inode number', + name: 'auditd.paths.inode', + type: 'keyword', + }, + 'auditd.paths.dev': { + category: 'auditd', + description: 'device name as found in /dev', + name: 'auditd.paths.dev', + type: 'keyword', + }, + 'auditd.paths.obj_user': { + category: 'auditd', + description: '', + name: 'auditd.paths.obj_user', + type: 'keyword', + }, + 'auditd.paths.obj_role': { + category: 'auditd', + description: '', + name: 'auditd.paths.obj_role', + type: 'keyword', + }, + 'auditd.paths.obj_domain': { + category: 'auditd', + description: '', + name: 'auditd.paths.obj_domain', + type: 'keyword', + }, + 'auditd.paths.obj_level': { + category: 'auditd', + description: '', + name: 'auditd.paths.obj_level', + type: 'keyword', + }, + 'auditd.paths.objtype': { + category: 'auditd', + description: '', + name: 'auditd.paths.objtype', + type: 'keyword', + }, + 'auditd.paths.ouid': { + category: 'auditd', + description: 'file owner user ID', + name: 'auditd.paths.ouid', + type: 'keyword', + }, + 'auditd.paths.rdev': { + category: 'auditd', + description: 'the device identifier (special files only)', + name: 'auditd.paths.rdev', + type: 'keyword', + }, + 'auditd.paths.nametype': { + category: 'auditd', + description: 'kind of file operation being referenced', + name: 'auditd.paths.nametype', + type: 'keyword', + }, + 'auditd.paths.ogid': { + category: 'auditd', + description: 'file owner group ID', + name: 'auditd.paths.ogid', + type: 'keyword', + }, + 'auditd.paths.item': { + category: 'auditd', + description: 'which item is being recorded', + name: 'auditd.paths.item', + type: 'keyword', + }, + 'auditd.paths.mode': { + category: 'auditd', + description: 'mode flags on a file', + name: 'auditd.paths.mode', + type: 'keyword', + }, + 'auditd.paths.name': { + category: 'auditd', + description: 'file name in avcs', + name: 'auditd.paths.name', + type: 'keyword', + }, + 'auditd.data.action': { + category: 'auditd', + description: 'netfilter packet disposition', + name: 'auditd.data.action', + type: 'keyword', + }, + 'auditd.data.minor': { + category: 'auditd', + description: 'device minor number', + name: 'auditd.data.minor', + type: 'keyword', + }, + 'auditd.data.acct': { + category: 'auditd', + description: "a user's account name", + name: 'auditd.data.acct', + type: 'keyword', + }, + 'auditd.data.addr': { + category: 'auditd', + description: 'the remote address that the user is connecting from', + name: 'auditd.data.addr', + type: 'keyword', + }, + 'auditd.data.cipher': { + category: 'auditd', + description: 'name of crypto cipher selected', + name: 'auditd.data.cipher', + type: 'keyword', + }, + 'auditd.data.id': { + category: 'auditd', + description: 'during account changes', + name: 'auditd.data.id', + type: 'keyword', + }, + 'auditd.data.entries': { + category: 'auditd', + description: 'number of entries in the netfilter table', + name: 'auditd.data.entries', + type: 'keyword', + }, + 'auditd.data.kind': { + category: 'auditd', + description: 'server or client in crypto operation', + name: 'auditd.data.kind', + type: 'keyword', + }, + 'auditd.data.ksize': { + category: 'auditd', + description: 'key size for crypto operation', + name: 'auditd.data.ksize', + type: 'keyword', + }, + 'auditd.data.spid': { + category: 'auditd', + description: 'sent process ID', + name: 'auditd.data.spid', + type: 'keyword', + }, + 'auditd.data.arch': { + category: 'auditd', + description: 'the elf architecture flags', + name: 'auditd.data.arch', + type: 'keyword', + }, + 'auditd.data.argc': { + category: 'auditd', + description: 'the number of arguments to an execve syscall', + name: 'auditd.data.argc', + type: 'keyword', + }, + 'auditd.data.major': { + category: 'auditd', + description: 'device major number', + name: 'auditd.data.major', + type: 'keyword', + }, + 'auditd.data.unit': { + category: 'auditd', + description: 'systemd unit', + name: 'auditd.data.unit', + type: 'keyword', + }, + 'auditd.data.table': { + category: 'auditd', + description: 'netfilter table name', + name: 'auditd.data.table', + type: 'keyword', + }, + 'auditd.data.terminal': { + category: 'auditd', + description: 'terminal name the user is running programs on', + name: 'auditd.data.terminal', + type: 'keyword', + }, + 'auditd.data.grantors': { + category: 'auditd', + description: 'pam modules approving the action', + name: 'auditd.data.grantors', + type: 'keyword', + }, + 'auditd.data.direction': { + category: 'auditd', + description: 'direction of crypto operation', + name: 'auditd.data.direction', + type: 'keyword', + }, + 'auditd.data.op': { + category: 'auditd', + description: 'the operation being performed that is audited', + name: 'auditd.data.op', + type: 'keyword', + }, + 'auditd.data.tty': { + category: 'auditd', + description: 'tty udevice the user is running programs on', + name: 'auditd.data.tty', + type: 'keyword', + }, + 'auditd.data.syscall': { + category: 'auditd', + description: 'syscall number in effect when the event occurred', + name: 'auditd.data.syscall', + type: 'keyword', + }, + 'auditd.data.data': { + category: 'auditd', + description: 'TTY text', + name: 'auditd.data.data', + type: 'keyword', + }, + 'auditd.data.family': { + category: 'auditd', + description: 'netfilter protocol', + name: 'auditd.data.family', + type: 'keyword', + }, + 'auditd.data.mac': { + category: 'auditd', + description: 'crypto MAC algorithm selected', + name: 'auditd.data.mac', + type: 'keyword', + }, + 'auditd.data.pfs': { + category: 'auditd', + description: 'perfect forward secrecy method', + name: 'auditd.data.pfs', + type: 'keyword', + }, + 'auditd.data.items': { + category: 'auditd', + description: 'the number of path records in the event', + name: 'auditd.data.items', + type: 'keyword', + }, + 'auditd.data.a0': { + category: 'auditd', + description: '', + name: 'auditd.data.a0', + type: 'keyword', + }, + 'auditd.data.a1': { + category: 'auditd', + description: '', + name: 'auditd.data.a1', + type: 'keyword', + }, + 'auditd.data.a2': { + category: 'auditd', + description: '', + name: 'auditd.data.a2', + type: 'keyword', + }, + 'auditd.data.a3': { + category: 'auditd', + description: '', + name: 'auditd.data.a3', + type: 'keyword', + }, + 'auditd.data.hostname': { + category: 'auditd', + description: 'the hostname that the user is connecting from', + name: 'auditd.data.hostname', + type: 'keyword', + }, + 'auditd.data.lport': { + category: 'auditd', + description: 'local network port', + name: 'auditd.data.lport', + type: 'keyword', + }, + 'auditd.data.rport': { + category: 'auditd', + description: 'remote port number', + name: 'auditd.data.rport', + type: 'keyword', + }, + 'auditd.data.exit': { + category: 'auditd', + description: 'syscall exit code', + name: 'auditd.data.exit', + type: 'keyword', + }, + 'auditd.data.fp': { + category: 'auditd', + description: 'crypto key finger print', + name: 'auditd.data.fp', + type: 'keyword', + }, + 'auditd.data.laddr': { + category: 'auditd', + description: 'local network address', + name: 'auditd.data.laddr', + type: 'keyword', + }, + 'auditd.data.sport': { + category: 'auditd', + description: 'local port number', + name: 'auditd.data.sport', + type: 'keyword', + }, + 'auditd.data.capability': { + category: 'auditd', + description: 'posix capabilities', + name: 'auditd.data.capability', + type: 'keyword', + }, + 'auditd.data.nargs': { + category: 'auditd', + description: 'the number of arguments to a socket call', + name: 'auditd.data.nargs', + type: 'keyword', + }, + 'auditd.data.new-enabled': { + category: 'auditd', + description: 'new TTY audit enabled setting', + name: 'auditd.data.new-enabled', + type: 'keyword', + }, + 'auditd.data.audit_backlog_limit': { + category: 'auditd', + description: "audit system's backlog queue size", + name: 'auditd.data.audit_backlog_limit', + type: 'keyword', + }, + 'auditd.data.dir': { + category: 'auditd', + description: 'directory name', + name: 'auditd.data.dir', + type: 'keyword', + }, + 'auditd.data.cap_pe': { + category: 'auditd', + description: 'process effective capability map', + name: 'auditd.data.cap_pe', + type: 'keyword', + }, + 'auditd.data.model': { + category: 'auditd', + description: 'security model being used for virt', + name: 'auditd.data.model', + type: 'keyword', + }, + 'auditd.data.new_pp': { + category: 'auditd', + description: 'new process permitted capability map', + name: 'auditd.data.new_pp', + type: 'keyword', + }, + 'auditd.data.old-enabled': { + category: 'auditd', + description: 'present TTY audit enabled setting', + name: 'auditd.data.old-enabled', + type: 'keyword', + }, + 'auditd.data.oauid': { + category: 'auditd', + description: "object's login user ID", + name: 'auditd.data.oauid', + type: 'keyword', + }, + 'auditd.data.old': { + category: 'auditd', + description: 'old value', + name: 'auditd.data.old', + type: 'keyword', + }, + 'auditd.data.banners': { + category: 'auditd', + description: 'banners used on printed page', + name: 'auditd.data.banners', + type: 'keyword', + }, + 'auditd.data.feature': { + category: 'auditd', + description: 'kernel feature being changed', + name: 'auditd.data.feature', + type: 'keyword', + }, + 'auditd.data.vm-ctx': { + category: 'auditd', + description: "the vm's context string", + name: 'auditd.data.vm-ctx', + type: 'keyword', + }, + 'auditd.data.opid': { + category: 'auditd', + description: "object's process ID", + name: 'auditd.data.opid', + type: 'keyword', + }, + 'auditd.data.seperms': { + category: 'auditd', + description: 'SELinux permissions being used', + name: 'auditd.data.seperms', + type: 'keyword', + }, + 'auditd.data.seresult': { + category: 'auditd', + description: 'SELinux AVC decision granted/denied', + name: 'auditd.data.seresult', + type: 'keyword', + }, + 'auditd.data.new-rng': { + category: 'auditd', + description: 'device name of rng being added from a vm', + name: 'auditd.data.new-rng', + type: 'keyword', + }, + 'auditd.data.old-net': { + category: 'auditd', + description: 'present MAC address assigned to vm', + name: 'auditd.data.old-net', + type: 'keyword', + }, + 'auditd.data.sigev_signo': { + category: 'auditd', + description: 'signal number', + name: 'auditd.data.sigev_signo', + type: 'keyword', + }, + 'auditd.data.ino': { + category: 'auditd', + description: 'inode number', + name: 'auditd.data.ino', + type: 'keyword', + }, + 'auditd.data.old_enforcing': { + category: 'auditd', + description: 'old MAC enforcement status', + name: 'auditd.data.old_enforcing', + type: 'keyword', + }, + 'auditd.data.old-vcpu': { + category: 'auditd', + description: 'present number of CPU cores', + name: 'auditd.data.old-vcpu', + type: 'keyword', + }, + 'auditd.data.range': { + category: 'auditd', + description: "user's SE Linux range", + name: 'auditd.data.range', + type: 'keyword', + }, + 'auditd.data.res': { + category: 'auditd', + description: 'result of the audited operation(success/fail)', + name: 'auditd.data.res', + type: 'keyword', + }, + 'auditd.data.added': { + category: 'auditd', + description: 'number of new files detected', + name: 'auditd.data.added', + type: 'keyword', + }, + 'auditd.data.fam': { + category: 'auditd', + description: 'socket address family', + name: 'auditd.data.fam', + type: 'keyword', + }, + 'auditd.data.nlnk-pid': { + category: 'auditd', + description: 'pid of netlink packet sender', + name: 'auditd.data.nlnk-pid', + type: 'keyword', + }, + 'auditd.data.subj': { + category: 'auditd', + description: "lspp subject's context string", + name: 'auditd.data.subj', + type: 'keyword', + }, + 'auditd.data.a[0-3]': { + category: 'auditd', + description: 'the arguments to a syscall', + name: 'auditd.data.a[0-3]', + type: 'keyword', + }, + 'auditd.data.cgroup': { + category: 'auditd', + description: 'path to cgroup in sysfs', + name: 'auditd.data.cgroup', + type: 'keyword', + }, + 'auditd.data.kernel': { + category: 'auditd', + description: "kernel's version number", + name: 'auditd.data.kernel', + type: 'keyword', + }, + 'auditd.data.ocomm': { + category: 'auditd', + description: "object's command line name", + name: 'auditd.data.ocomm', + type: 'keyword', + }, + 'auditd.data.new-net': { + category: 'auditd', + description: 'MAC address being assigned to vm', + name: 'auditd.data.new-net', + type: 'keyword', + }, + 'auditd.data.permissive': { + category: 'auditd', + description: 'SELinux is in permissive mode', + name: 'auditd.data.permissive', + type: 'keyword', + }, + 'auditd.data.class': { + category: 'auditd', + description: 'resource class assigned to vm', + name: 'auditd.data.class', + type: 'keyword', + }, + 'auditd.data.compat': { + category: 'auditd', + description: 'is_compat_task result', + name: 'auditd.data.compat', + type: 'keyword', + }, + 'auditd.data.fi': { + category: 'auditd', + description: 'file assigned inherited capability map', + name: 'auditd.data.fi', + type: 'keyword', + }, + 'auditd.data.changed': { + category: 'auditd', + description: 'number of changed files', + name: 'auditd.data.changed', + type: 'keyword', + }, + 'auditd.data.msg': { + category: 'auditd', + description: 'the payload of the audit record', + name: 'auditd.data.msg', + type: 'keyword', + }, + 'auditd.data.dport': { + category: 'auditd', + description: 'remote port number', + name: 'auditd.data.dport', + type: 'keyword', + }, + 'auditd.data.new-seuser': { + category: 'auditd', + description: 'new SELinux user', + name: 'auditd.data.new-seuser', + type: 'keyword', + }, + 'auditd.data.invalid_context': { + category: 'auditd', + description: 'SELinux context', + name: 'auditd.data.invalid_context', + type: 'keyword', + }, + 'auditd.data.dmac': { + category: 'auditd', + description: 'remote MAC address', + name: 'auditd.data.dmac', + type: 'keyword', + }, + 'auditd.data.ipx-net': { + category: 'auditd', + description: 'IPX network number', + name: 'auditd.data.ipx-net', + type: 'keyword', + }, + 'auditd.data.iuid': { + category: 'auditd', + description: "ipc object's user ID", + name: 'auditd.data.iuid', + type: 'keyword', + }, + 'auditd.data.macproto': { + category: 'auditd', + description: 'ethernet packet type ID field', + name: 'auditd.data.macproto', + type: 'keyword', + }, + 'auditd.data.obj': { + category: 'auditd', + description: 'lspp object context string', + name: 'auditd.data.obj', + type: 'keyword', + }, + 'auditd.data.ipid': { + category: 'auditd', + description: 'IP datagram fragment identifier', + name: 'auditd.data.ipid', + type: 'keyword', + }, + 'auditd.data.new-fs': { + category: 'auditd', + description: 'file system being added to vm', + name: 'auditd.data.new-fs', + type: 'keyword', + }, + 'auditd.data.vm-pid': { + category: 'auditd', + description: "vm's process ID", + name: 'auditd.data.vm-pid', + type: 'keyword', + }, + 'auditd.data.cap_pi': { + category: 'auditd', + description: 'process inherited capability map', + name: 'auditd.data.cap_pi', + type: 'keyword', + }, + 'auditd.data.old-auid': { + category: 'auditd', + description: 'previous auid value', + name: 'auditd.data.old-auid', + type: 'keyword', + }, + 'auditd.data.oses': { + category: 'auditd', + description: "object's session ID", + name: 'auditd.data.oses', + type: 'keyword', + }, + 'auditd.data.fd': { + category: 'auditd', + description: 'file descriptor number', + name: 'auditd.data.fd', + type: 'keyword', + }, + 'auditd.data.igid': { + category: 'auditd', + description: "ipc object's group ID", + name: 'auditd.data.igid', + type: 'keyword', + }, + 'auditd.data.new-disk': { + category: 'auditd', + description: 'disk being added to vm', + name: 'auditd.data.new-disk', + type: 'keyword', + }, + 'auditd.data.parent': { + category: 'auditd', + description: 'the inode number of the parent file', + name: 'auditd.data.parent', + type: 'keyword', + }, + 'auditd.data.len': { + category: 'auditd', + description: 'length', + name: 'auditd.data.len', + type: 'keyword', + }, + 'auditd.data.oflag': { + category: 'auditd', + description: 'open syscall flags', + name: 'auditd.data.oflag', + type: 'keyword', + }, + 'auditd.data.uuid': { + category: 'auditd', + description: 'a UUID', + name: 'auditd.data.uuid', + type: 'keyword', + }, + 'auditd.data.code': { + category: 'auditd', + description: 'seccomp action code', + name: 'auditd.data.code', + type: 'keyword', + }, + 'auditd.data.nlnk-grp': { + category: 'auditd', + description: 'netlink group number', + name: 'auditd.data.nlnk-grp', + type: 'keyword', + }, + 'auditd.data.cap_fp': { + category: 'auditd', + description: 'file permitted capability map', + name: 'auditd.data.cap_fp', + type: 'keyword', + }, + 'auditd.data.new-mem': { + category: 'auditd', + description: 'new amount of memory in KB', + name: 'auditd.data.new-mem', + type: 'keyword', + }, + 'auditd.data.seperm': { + category: 'auditd', + description: 'SELinux permission being decided on', + name: 'auditd.data.seperm', + type: 'keyword', + }, + 'auditd.data.enforcing': { + category: 'auditd', + description: 'new MAC enforcement status', + name: 'auditd.data.enforcing', + type: 'keyword', + }, + 'auditd.data.new-chardev': { + category: 'auditd', + description: 'new character device being assigned to vm', + name: 'auditd.data.new-chardev', + type: 'keyword', + }, + 'auditd.data.old-rng': { + category: 'auditd', + description: 'device name of rng being removed from a vm', + name: 'auditd.data.old-rng', + type: 'keyword', + }, + 'auditd.data.outif': { + category: 'auditd', + description: 'out interface number', + name: 'auditd.data.outif', + type: 'keyword', + }, + 'auditd.data.cmd': { + category: 'auditd', + description: 'command being executed', + name: 'auditd.data.cmd', + type: 'keyword', + }, + 'auditd.data.hook': { + category: 'auditd', + description: 'netfilter hook that packet came from', + name: 'auditd.data.hook', + type: 'keyword', + }, + 'auditd.data.new-level': { + category: 'auditd', + description: 'new run level', + name: 'auditd.data.new-level', + type: 'keyword', + }, + 'auditd.data.sauid': { + category: 'auditd', + description: 'sent login user ID', + name: 'auditd.data.sauid', + type: 'keyword', + }, + 'auditd.data.sig': { + category: 'auditd', + description: 'signal number', + name: 'auditd.data.sig', + type: 'keyword', + }, + 'auditd.data.audit_backlog_wait_time': { + category: 'auditd', + description: "audit system's backlog wait time", + name: 'auditd.data.audit_backlog_wait_time', + type: 'keyword', + }, + 'auditd.data.printer': { + category: 'auditd', + description: 'printer name', + name: 'auditd.data.printer', + type: 'keyword', + }, + 'auditd.data.old-mem': { + category: 'auditd', + description: 'present amount of memory in KB', + name: 'auditd.data.old-mem', + type: 'keyword', + }, + 'auditd.data.perm': { + category: 'auditd', + description: 'the file permission being used', + name: 'auditd.data.perm', + type: 'keyword', + }, + 'auditd.data.old_pi': { + category: 'auditd', + description: 'old process inherited capability map', + name: 'auditd.data.old_pi', + type: 'keyword', + }, + 'auditd.data.state': { + category: 'auditd', + description: 'audit daemon configuration resulting state', + name: 'auditd.data.state', + type: 'keyword', + }, + 'auditd.data.format': { + category: 'auditd', + description: "audit log's format", + name: 'auditd.data.format', + type: 'keyword', + }, + 'auditd.data.new_gid': { + category: 'auditd', + description: 'new group ID being assigned', + name: 'auditd.data.new_gid', + type: 'keyword', + }, + 'auditd.data.tcontext': { + category: 'auditd', + description: "the target's or object's context string", + name: 'auditd.data.tcontext', + type: 'keyword', + }, + 'auditd.data.maj': { + category: 'auditd', + description: 'device major number', + name: 'auditd.data.maj', + type: 'keyword', + }, + 'auditd.data.watch': { + category: 'auditd', + description: 'file name in a watch record', + name: 'auditd.data.watch', + type: 'keyword', + }, + 'auditd.data.device': { + category: 'auditd', + description: 'device name', + name: 'auditd.data.device', + type: 'keyword', + }, + 'auditd.data.grp': { + category: 'auditd', + description: 'group name', + name: 'auditd.data.grp', + type: 'keyword', + }, + 'auditd.data.bool': { + category: 'auditd', + description: 'name of SELinux boolean', + name: 'auditd.data.bool', + type: 'keyword', + }, + 'auditd.data.icmp_type': { + category: 'auditd', + description: 'type of icmp message', + name: 'auditd.data.icmp_type', + type: 'keyword', + }, + 'auditd.data.new_lock': { + category: 'auditd', + description: 'new value of feature lock', + name: 'auditd.data.new_lock', + type: 'keyword', + }, + 'auditd.data.old_prom': { + category: 'auditd', + description: 'network promiscuity flag', + name: 'auditd.data.old_prom', + type: 'keyword', + }, + 'auditd.data.acl': { + category: 'auditd', + description: 'access mode of resource assigned to vm', + name: 'auditd.data.acl', + type: 'keyword', + }, + 'auditd.data.ip': { + category: 'auditd', + description: 'network address of a printer', + name: 'auditd.data.ip', + type: 'keyword', + }, + 'auditd.data.new_pi': { + category: 'auditd', + description: 'new process inherited capability map', + name: 'auditd.data.new_pi', + type: 'keyword', + }, + 'auditd.data.default-context': { + category: 'auditd', + description: 'default MAC context', + name: 'auditd.data.default-context', + type: 'keyword', + }, + 'auditd.data.inode_gid': { + category: 'auditd', + description: "group ID of the inode's owner", + name: 'auditd.data.inode_gid', + type: 'keyword', + }, + 'auditd.data.new-log_passwd': { + category: 'auditd', + description: 'new value for TTY password logging', + name: 'auditd.data.new-log_passwd', + type: 'keyword', + }, + 'auditd.data.new_pe': { + category: 'auditd', + description: 'new process effective capability map', + name: 'auditd.data.new_pe', + type: 'keyword', + }, + 'auditd.data.selected-context': { + category: 'auditd', + description: 'new MAC context assigned to session', + name: 'auditd.data.selected-context', + type: 'keyword', + }, + 'auditd.data.cap_fver': { + category: 'auditd', + description: 'file system capabilities version number', + name: 'auditd.data.cap_fver', + type: 'keyword', + }, + 'auditd.data.file': { + category: 'auditd', + description: 'file name', + name: 'auditd.data.file', + type: 'keyword', + }, + 'auditd.data.net': { + category: 'auditd', + description: 'network MAC address', + name: 'auditd.data.net', + type: 'keyword', + }, + 'auditd.data.virt': { + category: 'auditd', + description: 'kind of virtualization being referenced', + name: 'auditd.data.virt', + type: 'keyword', + }, + 'auditd.data.cap_pp': { + category: 'auditd', + description: 'process permitted capability map', + name: 'auditd.data.cap_pp', + type: 'keyword', + }, + 'auditd.data.old-range': { + category: 'auditd', + description: 'present SELinux range', + name: 'auditd.data.old-range', + type: 'keyword', + }, + 'auditd.data.resrc': { + category: 'auditd', + description: 'resource being assigned', + name: 'auditd.data.resrc', + type: 'keyword', + }, + 'auditd.data.new-range': { + category: 'auditd', + description: 'new SELinux range', + name: 'auditd.data.new-range', + type: 'keyword', + }, + 'auditd.data.obj_gid': { + category: 'auditd', + description: 'group ID of object', + name: 'auditd.data.obj_gid', + type: 'keyword', + }, + 'auditd.data.proto': { + category: 'auditd', + description: 'network protocol', + name: 'auditd.data.proto', + type: 'keyword', + }, + 'auditd.data.old-disk': { + category: 'auditd', + description: 'disk being removed from vm', + name: 'auditd.data.old-disk', + type: 'keyword', + }, + 'auditd.data.audit_failure': { + category: 'auditd', + description: "audit system's failure mode", + name: 'auditd.data.audit_failure', + type: 'keyword', + }, + 'auditd.data.inif': { + category: 'auditd', + description: 'in interface number', + name: 'auditd.data.inif', + type: 'keyword', + }, + 'auditd.data.vm': { + category: 'auditd', + description: 'virtual machine name', + name: 'auditd.data.vm', + type: 'keyword', + }, + 'auditd.data.flags': { + category: 'auditd', + description: 'mmap syscall flags', + name: 'auditd.data.flags', + type: 'keyword', + }, + 'auditd.data.nlnk-fam': { + category: 'auditd', + description: 'netlink protocol number', + name: 'auditd.data.nlnk-fam', + type: 'keyword', + }, + 'auditd.data.old-fs': { + category: 'auditd', + description: 'file system being removed from vm', + name: 'auditd.data.old-fs', + type: 'keyword', + }, + 'auditd.data.old-ses': { + category: 'auditd', + description: 'previous ses value', + name: 'auditd.data.old-ses', + type: 'keyword', + }, + 'auditd.data.seqno': { + category: 'auditd', + description: 'sequence number', + name: 'auditd.data.seqno', + type: 'keyword', + }, + 'auditd.data.fver': { + category: 'auditd', + description: 'file system capabilities version number', + name: 'auditd.data.fver', + type: 'keyword', + }, + 'auditd.data.qbytes': { + category: 'auditd', + description: 'ipc objects quantity of bytes', + name: 'auditd.data.qbytes', + type: 'keyword', + }, + 'auditd.data.seuser': { + category: 'auditd', + description: "user's SE Linux user acct", + name: 'auditd.data.seuser', + type: 'keyword', + }, + 'auditd.data.cap_fe': { + category: 'auditd', + description: 'file assigned effective capability map', + name: 'auditd.data.cap_fe', + type: 'keyword', + }, + 'auditd.data.new-vcpu': { + category: 'auditd', + description: 'new number of CPU cores', + name: 'auditd.data.new-vcpu', + type: 'keyword', + }, + 'auditd.data.old-level': { + category: 'auditd', + description: 'old run level', + name: 'auditd.data.old-level', + type: 'keyword', + }, + 'auditd.data.old_pp': { + category: 'auditd', + description: 'old process permitted capability map', + name: 'auditd.data.old_pp', + type: 'keyword', + }, + 'auditd.data.daddr': { + category: 'auditd', + description: 'remote IP address', + name: 'auditd.data.daddr', + type: 'keyword', + }, + 'auditd.data.old-role': { + category: 'auditd', + description: 'present SELinux role', + name: 'auditd.data.old-role', + type: 'keyword', + }, + 'auditd.data.ioctlcmd': { + category: 'auditd', + description: 'The request argument to the ioctl syscall', + name: 'auditd.data.ioctlcmd', + type: 'keyword', + }, + 'auditd.data.smac': { + category: 'auditd', + description: 'local MAC address', + name: 'auditd.data.smac', + type: 'keyword', + }, + 'auditd.data.apparmor': { + category: 'auditd', + description: 'apparmor event information', + name: 'auditd.data.apparmor', + type: 'keyword', + }, + 'auditd.data.fe': { + category: 'auditd', + description: 'file assigned effective capability map', + name: 'auditd.data.fe', + type: 'keyword', + }, + 'auditd.data.perm_mask': { + category: 'auditd', + description: 'file permission mask that triggered a watch event', + name: 'auditd.data.perm_mask', + type: 'keyword', + }, + 'auditd.data.ses': { + category: 'auditd', + description: 'login session ID', + name: 'auditd.data.ses', + type: 'keyword', + }, + 'auditd.data.cap_fi': { + category: 'auditd', + description: 'file inherited capability map', + name: 'auditd.data.cap_fi', + type: 'keyword', + }, + 'auditd.data.obj_uid': { + category: 'auditd', + description: 'user ID of object', + name: 'auditd.data.obj_uid', + type: 'keyword', + }, + 'auditd.data.reason': { + category: 'auditd', + description: 'text string denoting a reason for the action', + name: 'auditd.data.reason', + type: 'keyword', + }, + 'auditd.data.list': { + category: 'auditd', + description: "the audit system's filter list number", + name: 'auditd.data.list', + type: 'keyword', + }, + 'auditd.data.old_lock': { + category: 'auditd', + description: 'present value of feature lock', + name: 'auditd.data.old_lock', + type: 'keyword', + }, + 'auditd.data.bus': { + category: 'auditd', + description: 'name of subsystem bus a vm resource belongs to', + name: 'auditd.data.bus', + type: 'keyword', + }, + 'auditd.data.old_pe': { + category: 'auditd', + description: 'old process effective capability map', + name: 'auditd.data.old_pe', + type: 'keyword', + }, + 'auditd.data.new-role': { + category: 'auditd', + description: 'new SELinux role', + name: 'auditd.data.new-role', + type: 'keyword', + }, + 'auditd.data.prom': { + category: 'auditd', + description: 'network promiscuity flag', + name: 'auditd.data.prom', + type: 'keyword', + }, + 'auditd.data.uri': { + category: 'auditd', + description: 'URI pointing to a printer', + name: 'auditd.data.uri', + type: 'keyword', + }, + 'auditd.data.audit_enabled': { + category: 'auditd', + description: "audit systems's enable/disable status", + name: 'auditd.data.audit_enabled', + type: 'keyword', + }, + 'auditd.data.old-log_passwd': { + category: 'auditd', + description: 'present value for TTY password logging', + name: 'auditd.data.old-log_passwd', + type: 'keyword', + }, + 'auditd.data.old-seuser': { + category: 'auditd', + description: 'present SELinux user', + name: 'auditd.data.old-seuser', + type: 'keyword', + }, + 'auditd.data.per': { + category: 'auditd', + description: 'linux personality', + name: 'auditd.data.per', + type: 'keyword', + }, + 'auditd.data.scontext': { + category: 'auditd', + description: "the subject's context string", + name: 'auditd.data.scontext', + type: 'keyword', + }, + 'auditd.data.tclass': { + category: 'auditd', + description: "target's object classification", + name: 'auditd.data.tclass', + type: 'keyword', + }, + 'auditd.data.ver': { + category: 'auditd', + description: "audit daemon's version number", + name: 'auditd.data.ver', + type: 'keyword', + }, + 'auditd.data.new': { + category: 'auditd', + description: 'value being set in feature', + name: 'auditd.data.new', + type: 'keyword', + }, + 'auditd.data.val': { + category: 'auditd', + description: 'generic value associated with the operation', + name: 'auditd.data.val', + type: 'keyword', + }, + 'auditd.data.img-ctx': { + category: 'auditd', + description: "the vm's disk image context string", + name: 'auditd.data.img-ctx', + type: 'keyword', + }, + 'auditd.data.old-chardev': { + category: 'auditd', + description: 'present character device assigned to vm', + name: 'auditd.data.old-chardev', + type: 'keyword', + }, + 'auditd.data.old_val': { + category: 'auditd', + description: 'current value of SELinux boolean', + name: 'auditd.data.old_val', + type: 'keyword', + }, + 'auditd.data.success': { + category: 'auditd', + description: 'whether the syscall was successful or not', + name: 'auditd.data.success', + type: 'keyword', + }, + 'auditd.data.inode_uid': { + category: 'auditd', + description: "user ID of the inode's owner", + name: 'auditd.data.inode_uid', + type: 'keyword', + }, + 'auditd.data.removed': { + category: 'auditd', + description: 'number of deleted files', + name: 'auditd.data.removed', + type: 'keyword', + }, + 'auditd.data.socket.port': { + category: 'auditd', + description: 'The port number.', + name: 'auditd.data.socket.port', + type: 'keyword', + }, + 'auditd.data.socket.saddr': { + category: 'auditd', + description: 'The raw socket address structure.', + name: 'auditd.data.socket.saddr', + type: 'keyword', + }, + 'auditd.data.socket.addr': { + category: 'auditd', + description: 'The remote address.', + name: 'auditd.data.socket.addr', + type: 'keyword', + }, + 'auditd.data.socket.family': { + category: 'auditd', + description: 'The socket family (unix, ipv4, ipv6, netlink).', + example: 'unix', + name: 'auditd.data.socket.family', + type: 'keyword', + }, + 'auditd.data.socket.path': { + category: 'auditd', + description: 'This is the path associated with a unix socket.', + name: 'auditd.data.socket.path', + type: 'keyword', + }, + 'auditd.messages': { + category: 'auditd', + description: + 'An ordered list of the raw messages received from the kernel that were used to construct this document. This field is present if an error occurred processing the data or if `include_raw_message` is set in the config. ', + name: 'auditd.messages', + type: 'alias', + }, + 'auditd.warnings': { + category: 'auditd', + description: + 'The warnings generated by the Beat during the construction of the event. These are disabled by default and are used for development and debug purposes only. ', + name: 'auditd.warnings', + type: 'alias', + }, + 'geoip.continent_name': { + category: 'geoip', + description: 'The name of the continent. ', + name: 'geoip.continent_name', + type: 'keyword', + }, + 'geoip.city_name': { + category: 'geoip', + description: 'The name of the city. ', + name: 'geoip.city_name', + type: 'keyword', + }, + 'geoip.region_name': { + category: 'geoip', + description: 'The name of the region. ', + name: 'geoip.region_name', + type: 'keyword', + }, + 'geoip.country_iso_code': { + category: 'geoip', + description: 'Country ISO code. ', + name: 'geoip.country_iso_code', + type: 'keyword', + }, + 'geoip.location': { + category: 'geoip', + description: 'The longitude and latitude. ', + name: 'geoip.location', + type: 'geo_point', + }, + 'hash.blake2b_256': { + category: 'hash', + description: 'BLAKE2b-256 hash of the file.', + name: 'hash.blake2b_256', + type: 'keyword', + }, + 'hash.blake2b_384': { + category: 'hash', + description: 'BLAKE2b-384 hash of the file.', + name: 'hash.blake2b_384', + type: 'keyword', + }, + 'hash.blake2b_512': { + category: 'hash', + description: 'BLAKE2b-512 hash of the file.', + name: 'hash.blake2b_512', + type: 'keyword', + }, + 'hash.sha224': { + category: 'hash', + description: 'SHA224 hash of the file.', + name: 'hash.sha224', + type: 'keyword', + }, + 'hash.sha384': { + category: 'hash', + description: 'SHA384 hash of the file.', + name: 'hash.sha384', + type: 'keyword', + }, + 'hash.sha3_224': { + category: 'hash', + description: 'SHA3_224 hash of the file.', + name: 'hash.sha3_224', + type: 'keyword', + }, + 'hash.sha3_256': { + category: 'hash', + description: 'SHA3_256 hash of the file.', + name: 'hash.sha3_256', + type: 'keyword', + }, + 'hash.sha3_384': { + category: 'hash', + description: 'SHA3_384 hash of the file.', + name: 'hash.sha3_384', + type: 'keyword', + }, + 'hash.sha3_512': { + category: 'hash', + description: 'SHA3_512 hash of the file.', + name: 'hash.sha3_512', + type: 'keyword', + }, + 'hash.sha512_224': { + category: 'hash', + description: 'SHA512/224 hash of the file.', + name: 'hash.sha512_224', + type: 'keyword', + }, + 'hash.sha512_256': { + category: 'hash', + description: 'SHA512/256 hash of the file.', + name: 'hash.sha512_256', + type: 'keyword', + }, + 'hash.xxh64': { + category: 'hash', + description: 'XX64 hash of the file.', + name: 'hash.xxh64', + type: 'keyword', + }, + 'event.origin': { + category: 'event', + description: + 'Origin of the event. This can be a file path (e.g. `/var/log/log.1`), or the name of the system component that supplied the data (e.g. `netlink`). ', + name: 'event.origin', + type: 'keyword', + }, + 'user.entity_id': { + category: 'user', + description: + 'ID uniquely identifying the user on a host. It is computed as a SHA-256 hash of the host ID, user ID, and user name. ', + name: 'user.entity_id', + type: 'keyword', + }, + 'user.terminal': { + category: 'user', + description: 'Terminal of the user. ', + name: 'user.terminal', + type: 'keyword', + }, + 'process.hash.blake2b_256': { + category: 'process', + description: 'BLAKE2b-256 hash of the executable.', + name: 'process.hash.blake2b_256', + type: 'keyword', + }, + 'process.hash.blake2b_384': { + category: 'process', + description: 'BLAKE2b-384 hash of the executable.', + name: 'process.hash.blake2b_384', + type: 'keyword', + }, + 'process.hash.blake2b_512': { + category: 'process', + description: 'BLAKE2b-512 hash of the executable.', + name: 'process.hash.blake2b_512', + type: 'keyword', + }, + 'process.hash.sha224': { + category: 'process', + description: 'SHA224 hash of the executable.', + name: 'process.hash.sha224', + type: 'keyword', + }, + 'process.hash.sha384': { + category: 'process', + description: 'SHA384 hash of the executable.', + name: 'process.hash.sha384', + type: 'keyword', + }, + 'process.hash.sha3_224': { + category: 'process', + description: 'SHA3_224 hash of the executable.', + name: 'process.hash.sha3_224', + type: 'keyword', + }, + 'process.hash.sha3_256': { + category: 'process', + description: 'SHA3_256 hash of the executable.', + name: 'process.hash.sha3_256', + type: 'keyword', + }, + 'process.hash.sha3_384': { + category: 'process', + description: 'SHA3_384 hash of the executable.', + name: 'process.hash.sha3_384', + type: 'keyword', + }, + 'process.hash.sha3_512': { + category: 'process', + description: 'SHA3_512 hash of the executable.', + name: 'process.hash.sha3_512', + type: 'keyword', + }, + 'process.hash.sha512_224': { + category: 'process', + description: 'SHA512/224 hash of the executable.', + name: 'process.hash.sha512_224', + type: 'keyword', + }, + 'process.hash.sha512_256': { + category: 'process', + description: 'SHA512/256 hash of the executable.', + name: 'process.hash.sha512_256', + type: 'keyword', + }, + 'process.hash.xxh64': { + category: 'process', + description: 'XX64 hash of the executable.', + name: 'process.hash.xxh64', + type: 'keyword', + }, + 'socket.entity_id': { + category: 'socket', + description: + 'ID uniquely identifying the socket. It is computed as a SHA-256 hash of the host ID, socket inode, local IP, local port, remote IP, and remote port. ', + name: 'socket.entity_id', + type: 'keyword', + }, + 'system.audit.host.uptime': { + category: 'system', + description: 'Uptime in nanoseconds. ', + name: 'system.audit.host.uptime', + type: 'long', + format: 'duration', + }, + 'system.audit.host.boottime': { + category: 'system', + description: 'Boot time. ', + name: 'system.audit.host.boottime', + type: 'date', + }, + 'system.audit.host.containerized': { + category: 'system', + description: 'Set if host is a container. ', + name: 'system.audit.host.containerized', + type: 'boolean', + }, + 'system.audit.host.timezone.name': { + category: 'system', + description: 'Name of the timezone of the host, e.g. BST. ', + name: 'system.audit.host.timezone.name', + type: 'keyword', + }, + 'system.audit.host.timezone.offset.sec': { + category: 'system', + description: 'Timezone offset in seconds. ', + name: 'system.audit.host.timezone.offset.sec', + type: 'long', + }, + 'system.audit.host.hostname': { + category: 'system', + description: 'Hostname. ', + name: 'system.audit.host.hostname', + type: 'keyword', + }, + 'system.audit.host.id': { + category: 'system', + description: 'Host ID. ', + name: 'system.audit.host.id', + type: 'keyword', + }, + 'system.audit.host.architecture': { + category: 'system', + description: 'Host architecture (e.g. x86_64). ', + name: 'system.audit.host.architecture', + type: 'keyword', + }, + 'system.audit.host.mac': { + category: 'system', + description: 'MAC addresses. ', + name: 'system.audit.host.mac', + type: 'keyword', + }, + 'system.audit.host.ip': { + category: 'system', + description: 'IP addresses. ', + name: 'system.audit.host.ip', + type: 'ip', + }, + 'system.audit.host.os.codename': { + category: 'system', + description: 'OS codename, if any (e.g. stretch). ', + name: 'system.audit.host.os.codename', + type: 'keyword', + }, + 'system.audit.host.os.platform': { + category: 'system', + description: 'OS platform (e.g. centos, ubuntu, windows). ', + name: 'system.audit.host.os.platform', + type: 'keyword', + }, + 'system.audit.host.os.name': { + category: 'system', + description: 'OS name (e.g. Mac OS X). ', + name: 'system.audit.host.os.name', + type: 'keyword', + }, + 'system.audit.host.os.family': { + category: 'system', + description: 'OS family (e.g. redhat, debian, freebsd, windows). ', + name: 'system.audit.host.os.family', + type: 'keyword', + }, + 'system.audit.host.os.version': { + category: 'system', + description: 'OS version. ', + name: 'system.audit.host.os.version', + type: 'keyword', + }, + 'system.audit.host.os.kernel': { + category: 'system', + description: "The operating system's kernel version. ", + name: 'system.audit.host.os.kernel', + type: 'keyword', + }, + 'system.audit.package.entity_id': { + category: 'system', + description: + 'ID uniquely identifying the package. It is computed as a SHA-256 hash of the host ID, package name, and package version. ', + name: 'system.audit.package.entity_id', + type: 'keyword', + }, + 'system.audit.package.name': { + category: 'system', + description: 'Package name. ', + name: 'system.audit.package.name', + type: 'keyword', + }, + 'system.audit.package.version': { + category: 'system', + description: 'Package version. ', + name: 'system.audit.package.version', + type: 'keyword', + }, + 'system.audit.package.release': { + category: 'system', + description: 'Package release. ', + name: 'system.audit.package.release', + type: 'keyword', + }, + 'system.audit.package.arch': { + category: 'system', + description: 'Package architecture. ', + name: 'system.audit.package.arch', + type: 'keyword', + }, + 'system.audit.package.license': { + category: 'system', + description: 'Package license. ', + name: 'system.audit.package.license', + type: 'keyword', + }, + 'system.audit.package.installtime': { + category: 'system', + description: 'Package install time. ', + name: 'system.audit.package.installtime', + type: 'date', + }, + 'system.audit.package.size': { + category: 'system', + description: 'Package size. ', + name: 'system.audit.package.size', + type: 'long', + }, + 'system.audit.package.summary': { + category: 'system', + description: 'Package summary. ', + name: 'system.audit.package.summary', + }, + 'system.audit.package.url': { + category: 'system', + description: 'Package URL. ', + name: 'system.audit.package.url', + type: 'keyword', + }, + 'system.audit.user.name': { + category: 'system', + description: 'User name. ', + name: 'system.audit.user.name', + type: 'keyword', + }, + 'system.audit.user.uid': { + category: 'system', + description: 'User ID. ', + name: 'system.audit.user.uid', + type: 'keyword', + }, + 'system.audit.user.gid': { + category: 'system', + description: 'Group ID. ', + name: 'system.audit.user.gid', + type: 'keyword', + }, + 'system.audit.user.dir': { + category: 'system', + description: "User's home directory. ", + name: 'system.audit.user.dir', + type: 'keyword', + }, + 'system.audit.user.shell': { + category: 'system', + description: 'Program to run at login. ', + name: 'system.audit.user.shell', + type: 'keyword', + }, + 'system.audit.user.user_information': { + category: 'system', + description: 'General user information. On Linux, this is the gecos field. ', + name: 'system.audit.user.user_information', + type: 'keyword', + }, + 'system.audit.user.group.name': { + category: 'system', + description: 'Group name. ', + name: 'system.audit.user.group.name', + type: 'keyword', + }, + 'system.audit.user.group.gid': { + category: 'system', + description: 'Group ID. ', + name: 'system.audit.user.group.gid', + type: 'integer', + }, + 'system.audit.user.password.type': { + category: 'system', + description: + "A user's password type. Possible values are `shadow_password` (the password hash is in the shadow file), `password_disabled`, `no_password` (this is dangerous as anyone can log in), and `crypt_password` (when the password field in /etc/passwd seems to contain an encrypted password). ", + name: 'system.audit.user.password.type', + type: 'keyword', + }, + 'system.audit.user.password.last_changed': { + category: 'system', + description: "The day the user's password was last changed. ", + name: 'system.audit.user.password.last_changed', + type: 'date', + }, + 'log.file.path': { + category: 'log', + description: + 'The file from which the line was read. This field contains the absolute path to the file. For example: `/var/log/system.log`. ', + name: 'log.file.path', + type: 'keyword', + }, + 'log.source.address': { + category: 'log', + description: 'Source address from which the log event was read / sent from. ', + name: 'log.source.address', + type: 'keyword', + }, + 'log.offset': { + category: 'log', + description: 'The file offset the reported line starts at. ', + name: 'log.offset', + type: 'long', + }, + stream: { + category: 'base', + description: "Log stream when reading container logs, can be 'stdout' or 'stderr' ", + name: 'stream', + type: 'keyword', + }, + 'input.type': { + category: 'input', + description: + 'The input type from which the event was generated. This field is set to the value specified for the `type` option in the input section of the Filebeat config file. ', + name: 'input.type', + }, + 'syslog.facility': { + category: 'syslog', + description: 'The facility extracted from the priority. ', + name: 'syslog.facility', + type: 'long', + }, + 'syslog.priority': { + category: 'syslog', + description: 'The priority of the syslog event. ', + name: 'syslog.priority', + type: 'long', + }, + 'syslog.severity_label': { + category: 'syslog', + description: 'The human readable severity. ', + name: 'syslog.severity_label', + type: 'keyword', + }, + 'syslog.facility_label': { + category: 'syslog', + description: 'The human readable facility. ', + name: 'syslog.facility_label', + type: 'keyword', + }, + 'process.program': { + category: 'process', + description: 'The name of the program. ', + name: 'process.program', + type: 'keyword', + }, + 'log.flags': { + category: 'log', + description: 'This field contains the flags of the event. ', + name: 'log.flags', + }, + 'http.response.content_length': { + category: 'http', + name: 'http.response.content_length', + type: 'alias', + }, + 'user_agent.os.full_name': { + category: 'user_agent', + name: 'user_agent.os.full_name', + type: 'keyword', + }, + 'fileset.name': { + category: 'fileset', + description: 'The Filebeat fileset that generated this event. ', + name: 'fileset.name', + type: 'keyword', + }, + 'fileset.module': { + category: 'fileset', + name: 'fileset.module', + type: 'alias', + }, + read_timestamp: { + category: 'base', + name: 'read_timestamp', + type: 'alias', + }, + 'docker.attrs': { + category: 'docker', + description: + "docker.attrs contains labels and environment variables written by docker's JSON File logging driver. These fields are only available when they are configured in the logging driver options. ", + name: 'docker.attrs', + type: 'object', + }, + 'icmp.code': { + category: 'icmp', + description: 'ICMP code. ', + name: 'icmp.code', + type: 'keyword', + }, + 'icmp.type': { + category: 'icmp', + description: 'ICMP type. ', + name: 'icmp.type', + type: 'keyword', + }, + 'igmp.type': { + category: 'igmp', + description: 'IGMP type. ', + name: 'igmp.type', + type: 'keyword', + }, + 'azure.eventhub': { + category: 'azure', + description: 'Name of the eventhub. ', + name: 'azure.eventhub', + type: 'keyword', + }, + 'azure.offset': { + category: 'azure', + description: 'The offset. ', + name: 'azure.offset', + type: 'long', + }, + 'azure.enqueued_time': { + category: 'azure', + description: 'The enqueued time. ', + name: 'azure.enqueued_time', + type: 'date', + }, + 'azure.partition_id': { + category: 'azure', + description: 'The partition id. ', + name: 'azure.partition_id', + type: 'long', + }, + 'azure.consumer_group': { + category: 'azure', + description: 'The consumer group. ', + name: 'azure.consumer_group', + type: 'keyword', + }, + 'azure.sequence_number': { + category: 'azure', + description: 'The sequence number. ', + name: 'azure.sequence_number', + type: 'long', + }, + 'kafka.topic': { + category: 'kafka', + description: 'Kafka topic ', + name: 'kafka.topic', + type: 'keyword', + }, + 'kafka.partition': { + category: 'kafka', + description: 'Kafka partition number ', + name: 'kafka.partition', + type: 'long', + }, + 'kafka.offset': { + category: 'kafka', + description: 'Kafka offset of this message ', + name: 'kafka.offset', + type: 'long', + }, + 'kafka.key': { + category: 'kafka', + description: 'Kafka key, corresponding to the Kafka value stored in the message ', + name: 'kafka.key', + type: 'keyword', + }, + 'kafka.block_timestamp': { + category: 'kafka', + description: 'Kafka outer (compressed) block timestamp ', + name: 'kafka.block_timestamp', + type: 'date', + }, + 'kafka.headers': { + category: 'kafka', + description: + 'An array of Kafka header strings for this message, in the form ": ". ', + name: 'kafka.headers', + type: 'array', + }, + 'apache2.access.remote_ip': { + category: 'apache2', + name: 'apache2.access.remote_ip', + type: 'alias', + }, + 'apache2.access.ssl.protocol': { + category: 'apache2', + name: 'apache2.access.ssl.protocol', + type: 'alias', + }, + 'apache2.access.ssl.cipher': { + category: 'apache2', + name: 'apache2.access.ssl.cipher', + type: 'alias', + }, + 'apache2.access.body_sent.bytes': { + category: 'apache2', + name: 'apache2.access.body_sent.bytes', + type: 'alias', + }, + 'apache2.access.user_name': { + category: 'apache2', + name: 'apache2.access.user_name', + type: 'alias', + }, + 'apache2.access.method': { + category: 'apache2', + name: 'apache2.access.method', + type: 'alias', + }, + 'apache2.access.url': { + category: 'apache2', + name: 'apache2.access.url', + type: 'alias', + }, + 'apache2.access.http_version': { + category: 'apache2', + name: 'apache2.access.http_version', + type: 'alias', + }, + 'apache2.access.response_code': { + category: 'apache2', + name: 'apache2.access.response_code', + type: 'alias', + }, + 'apache2.access.referrer': { + category: 'apache2', + name: 'apache2.access.referrer', + type: 'alias', + }, + 'apache2.access.agent': { + category: 'apache2', + name: 'apache2.access.agent', + type: 'alias', + }, + 'apache2.access.user_agent.device': { + category: 'apache2', + name: 'apache2.access.user_agent.device', + type: 'alias', + }, + 'apache2.access.user_agent.name': { + category: 'apache2', + name: 'apache2.access.user_agent.name', + type: 'alias', + }, + 'apache2.access.user_agent.os': { + category: 'apache2', + name: 'apache2.access.user_agent.os', + type: 'alias', + }, + 'apache2.access.user_agent.os_name': { + category: 'apache2', + name: 'apache2.access.user_agent.os_name', + type: 'alias', + }, + 'apache2.access.user_agent.original': { + category: 'apache2', + name: 'apache2.access.user_agent.original', + type: 'alias', + }, + 'apache2.access.geoip.continent_name': { + category: 'apache2', + name: 'apache2.access.geoip.continent_name', + type: 'alias', + }, + 'apache2.access.geoip.country_iso_code': { + category: 'apache2', + name: 'apache2.access.geoip.country_iso_code', + type: 'alias', + }, + 'apache2.access.geoip.location': { + category: 'apache2', + name: 'apache2.access.geoip.location', + type: 'alias', + }, + 'apache2.access.geoip.region_name': { + category: 'apache2', + name: 'apache2.access.geoip.region_name', + type: 'alias', + }, + 'apache2.access.geoip.city_name': { + category: 'apache2', + name: 'apache2.access.geoip.city_name', + type: 'alias', + }, + 'apache2.access.geoip.region_iso_code': { + category: 'apache2', + name: 'apache2.access.geoip.region_iso_code', + type: 'alias', + }, + 'apache2.error.level': { + category: 'apache2', + name: 'apache2.error.level', + type: 'alias', + }, + 'apache2.error.message': { + category: 'apache2', + name: 'apache2.error.message', + type: 'alias', + }, + 'apache2.error.pid': { + category: 'apache2', + name: 'apache2.error.pid', + type: 'alias', + }, + 'apache2.error.tid': { + category: 'apache2', + name: 'apache2.error.tid', + type: 'alias', + }, + 'apache2.error.module': { + category: 'apache2', + name: 'apache2.error.module', + type: 'alias', + }, + 'apache.access.ssl.protocol': { + category: 'apache', + description: 'SSL protocol version. ', + name: 'apache.access.ssl.protocol', + type: 'keyword', + }, + 'apache.access.ssl.cipher': { + category: 'apache', + description: 'SSL cipher name. ', + name: 'apache.access.ssl.cipher', + type: 'keyword', + }, + 'apache.error.module': { + category: 'apache', + description: 'The module producing the logged message. ', + name: 'apache.error.module', + type: 'keyword', + }, + 'user.audit.group.id': { + category: 'user', + description: 'Unique identifier for the group on the system/platform. ', + name: 'user.audit.group.id', + type: 'keyword', + }, + 'user.audit.group.name': { + category: 'user', + description: 'Name of the group. ', + name: 'user.audit.group.name', + type: 'keyword', + }, + 'user.owner.id': { + category: 'user', + description: 'One or multiple unique identifiers of the user. ', + name: 'user.owner.id', + type: 'keyword', + }, + 'user.owner.name': { + category: 'user', + description: 'Short name or login of the user. ', + example: 'albert', + name: 'user.owner.name', + type: 'keyword', + }, + 'user.owner.group.id': { + category: 'user', + description: 'Unique identifier for the group on the system/platform. ', + name: 'user.owner.group.id', + type: 'keyword', + }, + 'user.owner.group.name': { + category: 'user', + description: 'Name of the group. ', + name: 'user.owner.group.name', + type: 'keyword', + }, + 'auditd.log.old_auid': { + category: 'auditd', + description: + 'For login events this is the old audit ID used for the user prior to this login. ', + name: 'auditd.log.old_auid', + }, + 'auditd.log.new_auid': { + category: 'auditd', + description: + 'For login events this is the new audit ID. The audit ID can be used to trace future events to the user even if their identity changes (like becoming root). ', + name: 'auditd.log.new_auid', + }, + 'auditd.log.old_ses': { + category: 'auditd', + description: + 'For login events this is the old session ID used for the user prior to this login. ', + name: 'auditd.log.old_ses', + }, + 'auditd.log.new_ses': { + category: 'auditd', + description: + 'For login events this is the new session ID. It can be used to tie a user to future events by session ID. ', + name: 'auditd.log.new_ses', + }, + 'auditd.log.sequence': { + category: 'auditd', + description: 'The audit event sequence number. ', + name: 'auditd.log.sequence', + type: 'long', + }, + 'auditd.log.items': { + category: 'auditd', + description: 'The number of items in an event. ', + name: 'auditd.log.items', + }, + 'auditd.log.item': { + category: 'auditd', + description: + 'The item field indicates which item out of the total number of items. This number is zero-based; a value of 0 means it is the first item. ', + name: 'auditd.log.item', + }, + 'auditd.log.tty': { + category: 'auditd', + name: 'auditd.log.tty', + type: 'keyword', + }, + 'auditd.log.a0': { + category: 'auditd', + description: 'The first argument to the system call. ', + name: 'auditd.log.a0', + }, + 'auditd.log.addr': { + category: 'auditd', + name: 'auditd.log.addr', + type: 'ip', + }, + 'auditd.log.rport': { + category: 'auditd', + name: 'auditd.log.rport', + type: 'long', + }, + 'auditd.log.laddr': { + category: 'auditd', + name: 'auditd.log.laddr', + type: 'ip', + }, + 'auditd.log.lport': { + category: 'auditd', + name: 'auditd.log.lport', + type: 'long', + }, + 'auditd.log.acct': { + category: 'auditd', + name: 'auditd.log.acct', + type: 'alias', + }, + 'auditd.log.pid': { + category: 'auditd', + name: 'auditd.log.pid', + type: 'alias', + }, + 'auditd.log.ppid': { + category: 'auditd', + name: 'auditd.log.ppid', + type: 'alias', + }, + 'auditd.log.res': { + category: 'auditd', + name: 'auditd.log.res', + type: 'alias', + }, + 'auditd.log.record_type': { + category: 'auditd', + name: 'auditd.log.record_type', + type: 'alias', + }, + 'auditd.log.geoip.continent_name': { + category: 'auditd', + name: 'auditd.log.geoip.continent_name', + type: 'alias', + }, + 'auditd.log.geoip.country_iso_code': { + category: 'auditd', + name: 'auditd.log.geoip.country_iso_code', + type: 'alias', + }, + 'auditd.log.geoip.location': { + category: 'auditd', + name: 'auditd.log.geoip.location', + type: 'alias', + }, + 'auditd.log.geoip.region_name': { + category: 'auditd', + name: 'auditd.log.geoip.region_name', + type: 'alias', + }, + 'auditd.log.geoip.city_name': { + category: 'auditd', + name: 'auditd.log.geoip.city_name', + type: 'alias', + }, + 'auditd.log.geoip.region_iso_code': { + category: 'auditd', + name: 'auditd.log.geoip.region_iso_code', + type: 'alias', + }, + 'auditd.log.arch': { + category: 'auditd', + name: 'auditd.log.arch', + type: 'alias', + }, + 'auditd.log.gid': { + category: 'auditd', + name: 'auditd.log.gid', + type: 'alias', + }, + 'auditd.log.uid': { + category: 'auditd', + name: 'auditd.log.uid', + type: 'alias', + }, + 'auditd.log.agid': { + category: 'auditd', + name: 'auditd.log.agid', + type: 'alias', + }, + 'auditd.log.auid': { + category: 'auditd', + name: 'auditd.log.auid', + type: 'alias', + }, + 'auditd.log.fsgid': { + category: 'auditd', + name: 'auditd.log.fsgid', + type: 'alias', + }, + 'auditd.log.fsuid': { + category: 'auditd', + name: 'auditd.log.fsuid', + type: 'alias', + }, + 'auditd.log.egid': { + category: 'auditd', + name: 'auditd.log.egid', + type: 'alias', + }, + 'auditd.log.euid': { + category: 'auditd', + name: 'auditd.log.euid', + type: 'alias', + }, + 'auditd.log.sgid': { + category: 'auditd', + name: 'auditd.log.sgid', + type: 'alias', + }, + 'auditd.log.suid': { + category: 'auditd', + name: 'auditd.log.suid', + type: 'alias', + }, + 'auditd.log.ogid': { + category: 'auditd', + name: 'auditd.log.ogid', + type: 'alias', + }, + 'auditd.log.ouid': { + category: 'auditd', + name: 'auditd.log.ouid', + type: 'alias', + }, + 'auditd.log.comm': { + category: 'auditd', + name: 'auditd.log.comm', + type: 'alias', + }, + 'auditd.log.exe': { + category: 'auditd', + name: 'auditd.log.exe', + type: 'alias', + }, + 'auditd.log.terminal': { + category: 'auditd', + name: 'auditd.log.terminal', + type: 'alias', + }, + 'auditd.log.msg': { + category: 'auditd', + name: 'auditd.log.msg', + type: 'alias', + }, + 'auditd.log.src': { + category: 'auditd', + name: 'auditd.log.src', + type: 'alias', + }, + 'auditd.log.dst': { + category: 'auditd', + name: 'auditd.log.dst', + type: 'alias', + }, + 'elasticsearch.component': { + category: 'elasticsearch', + description: 'Elasticsearch component from where the log event originated', + example: 'o.e.c.m.MetaDataCreateIndexService', + name: 'elasticsearch.component', + type: 'keyword', + }, + 'elasticsearch.cluster.uuid': { + category: 'elasticsearch', + description: 'UUID of the cluster', + example: 'GmvrbHlNTiSVYiPf8kxg9g', + name: 'elasticsearch.cluster.uuid', + type: 'keyword', + }, + 'elasticsearch.cluster.name': { + category: 'elasticsearch', + description: 'Name of the cluster', + example: 'docker-cluster', + name: 'elasticsearch.cluster.name', + type: 'keyword', + }, + 'elasticsearch.node.id': { + category: 'elasticsearch', + description: 'ID of the node', + example: 'DSiWcTyeThWtUXLB9J0BMw', + name: 'elasticsearch.node.id', + type: 'keyword', + }, + 'elasticsearch.node.name': { + category: 'elasticsearch', + description: 'Name of the node', + example: 'vWNJsZ3', + name: 'elasticsearch.node.name', + type: 'keyword', + }, + 'elasticsearch.index.name': { + category: 'elasticsearch', + description: 'Index name', + example: 'filebeat-test-input', + name: 'elasticsearch.index.name', + type: 'keyword', + }, + 'elasticsearch.index.id': { + category: 'elasticsearch', + description: 'Index id', + example: 'aOGgDwbURfCV57AScqbCgw', + name: 'elasticsearch.index.id', + type: 'keyword', + }, + 'elasticsearch.shard.id': { + category: 'elasticsearch', + description: 'Id of the shard', + example: '0', + name: 'elasticsearch.shard.id', + type: 'keyword', + }, + 'elasticsearch.audit.layer': { + category: 'elasticsearch', + description: 'The layer from which this event originated: rest, transport or ip_filter', + example: 'rest', + name: 'elasticsearch.audit.layer', + type: 'keyword', + }, + 'elasticsearch.audit.event_type': { + category: 'elasticsearch', + description: + 'The type of event that occurred: anonymous_access_denied, authentication_failed, access_denied, access_granted, connection_granted, connection_denied, tampered_request, run_as_granted, run_as_denied', + example: 'access_granted', + name: 'elasticsearch.audit.event_type', + type: 'keyword', + }, + 'elasticsearch.audit.origin.type': { + category: 'elasticsearch', + description: + 'Where the request originated: rest (request originated from a REST API request), transport (request was received on the transport channel), local_node (the local node issued the request)', + example: 'local_node', + name: 'elasticsearch.audit.origin.type', + type: 'keyword', + }, + 'elasticsearch.audit.realm': { + category: 'elasticsearch', + description: 'The authentication realm the authentication was validated against', + name: 'elasticsearch.audit.realm', + type: 'keyword', + }, + 'elasticsearch.audit.user.realm': { + category: 'elasticsearch', + description: "The user's authentication realm, if authenticated", + name: 'elasticsearch.audit.user.realm', + type: 'keyword', + }, + 'elasticsearch.audit.user.roles': { + category: 'elasticsearch', + description: 'Roles to which the principal belongs', + example: '["kibana_admin","beats_admin"]', + name: 'elasticsearch.audit.user.roles', + type: 'keyword', + }, + 'elasticsearch.audit.action': { + category: 'elasticsearch', + description: 'The name of the action that was executed', + example: 'cluster:monitor/main', + name: 'elasticsearch.audit.action', + type: 'keyword', + }, + 'elasticsearch.audit.url.params': { + category: 'elasticsearch', + description: 'REST URI parameters', + example: '{username=jacknich2}', + name: 'elasticsearch.audit.url.params', + }, + 'elasticsearch.audit.indices': { + category: 'elasticsearch', + description: 'Indices accessed by action', + example: '["foo-2019.01.04","foo-2019.01.03","foo-2019.01.06"]', + name: 'elasticsearch.audit.indices', + type: 'keyword', + }, + 'elasticsearch.audit.request.id': { + category: 'elasticsearch', + description: 'Unique ID of request', + example: 'WzL_kb6VSvOhAq0twPvHOQ', + name: 'elasticsearch.audit.request.id', + type: 'keyword', + }, + 'elasticsearch.audit.request.name': { + category: 'elasticsearch', + description: 'The type of request that was executed', + example: 'ClearScrollRequest', + name: 'elasticsearch.audit.request.name', + type: 'keyword', + }, + 'elasticsearch.audit.request_body': { + category: 'elasticsearch', + name: 'elasticsearch.audit.request_body', + type: 'alias', + }, + 'elasticsearch.audit.origin_address': { + category: 'elasticsearch', + name: 'elasticsearch.audit.origin_address', + type: 'alias', + }, + 'elasticsearch.audit.uri': { + category: 'elasticsearch', + name: 'elasticsearch.audit.uri', + type: 'alias', + }, + 'elasticsearch.audit.principal': { + category: 'elasticsearch', + name: 'elasticsearch.audit.principal', + type: 'alias', + }, + 'elasticsearch.audit.message': { + category: 'elasticsearch', + name: 'elasticsearch.audit.message', + type: 'text', + }, + 'elasticsearch.deprecation': { + category: 'elasticsearch', + description: '', + name: 'elasticsearch.deprecation', + type: 'group', + }, + 'elasticsearch.gc.phase.name': { + category: 'elasticsearch', + description: 'Name of the GC collection phase. ', + name: 'elasticsearch.gc.phase.name', + type: 'keyword', + }, + 'elasticsearch.gc.phase.duration_sec': { + category: 'elasticsearch', + description: 'Collection phase duration according to the Java virtual machine. ', + name: 'elasticsearch.gc.phase.duration_sec', + type: 'float', + }, + 'elasticsearch.gc.phase.scrub_symbol_table_time_sec': { + category: 'elasticsearch', + description: 'Pause time in seconds cleaning up symbol tables. ', + name: 'elasticsearch.gc.phase.scrub_symbol_table_time_sec', + type: 'float', + }, + 'elasticsearch.gc.phase.scrub_string_table_time_sec': { + category: 'elasticsearch', + description: 'Pause time in seconds cleaning up string tables. ', + name: 'elasticsearch.gc.phase.scrub_string_table_time_sec', + type: 'float', + }, + 'elasticsearch.gc.phase.weak_refs_processing_time_sec': { + category: 'elasticsearch', + description: 'Time spent processing weak references in seconds. ', + name: 'elasticsearch.gc.phase.weak_refs_processing_time_sec', + type: 'float', + }, + 'elasticsearch.gc.phase.parallel_rescan_time_sec': { + category: 'elasticsearch', + description: 'Time spent in seconds marking live objects while application is stopped. ', + name: 'elasticsearch.gc.phase.parallel_rescan_time_sec', + type: 'float', + }, + 'elasticsearch.gc.phase.class_unload_time_sec': { + category: 'elasticsearch', + description: 'Time spent unloading unused classes in seconds. ', + name: 'elasticsearch.gc.phase.class_unload_time_sec', + type: 'float', + }, + 'elasticsearch.gc.phase.cpu_time.user_sec': { + category: 'elasticsearch', + description: 'CPU time spent outside the kernel. ', + name: 'elasticsearch.gc.phase.cpu_time.user_sec', + type: 'float', + }, + 'elasticsearch.gc.phase.cpu_time.sys_sec': { + category: 'elasticsearch', + description: 'CPU time spent inside the kernel. ', + name: 'elasticsearch.gc.phase.cpu_time.sys_sec', + type: 'float', + }, + 'elasticsearch.gc.phase.cpu_time.real_sec': { + category: 'elasticsearch', + description: 'Total elapsed CPU time spent to complete the collection from start to finish. ', + name: 'elasticsearch.gc.phase.cpu_time.real_sec', + type: 'float', + }, + 'elasticsearch.gc.jvm_runtime_sec': { + category: 'elasticsearch', + description: 'The time from JVM start up in seconds, as a floating point number. ', + name: 'elasticsearch.gc.jvm_runtime_sec', + type: 'float', + }, + 'elasticsearch.gc.threads_total_stop_time_sec': { + category: 'elasticsearch', + description: 'Garbage collection threads total stop time seconds. ', + name: 'elasticsearch.gc.threads_total_stop_time_sec', + type: 'float', + }, + 'elasticsearch.gc.stopping_threads_time_sec': { + category: 'elasticsearch', + description: 'Time took to stop threads seconds. ', + name: 'elasticsearch.gc.stopping_threads_time_sec', + type: 'float', + }, + 'elasticsearch.gc.tags': { + category: 'elasticsearch', + description: 'GC logging tags. ', + name: 'elasticsearch.gc.tags', + type: 'keyword', + }, + 'elasticsearch.gc.heap.size_kb': { + category: 'elasticsearch', + description: 'Total heap size in kilobytes. ', + name: 'elasticsearch.gc.heap.size_kb', + type: 'integer', + }, + 'elasticsearch.gc.heap.used_kb': { + category: 'elasticsearch', + description: 'Used heap in kilobytes. ', + name: 'elasticsearch.gc.heap.used_kb', + type: 'integer', + }, + 'elasticsearch.gc.old_gen.size_kb': { + category: 'elasticsearch', + description: 'Total size of old generation in kilobytes. ', + name: 'elasticsearch.gc.old_gen.size_kb', + type: 'integer', + }, + 'elasticsearch.gc.old_gen.used_kb': { + category: 'elasticsearch', + description: 'Old generation occupancy in kilobytes. ', + name: 'elasticsearch.gc.old_gen.used_kb', + type: 'integer', + }, + 'elasticsearch.gc.young_gen.size_kb': { + category: 'elasticsearch', + description: 'Total size of young generation in kilobytes. ', + name: 'elasticsearch.gc.young_gen.size_kb', + type: 'integer', + }, + 'elasticsearch.gc.young_gen.used_kb': { + category: 'elasticsearch', + description: 'Young generation occupancy in kilobytes. ', + name: 'elasticsearch.gc.young_gen.used_kb', + type: 'integer', + }, + 'elasticsearch.server.stacktrace': { + category: 'elasticsearch', + name: 'elasticsearch.server.stacktrace', + }, + 'elasticsearch.server.gc.young.one': { + category: 'elasticsearch', + description: '', + example: '', + name: 'elasticsearch.server.gc.young.one', + type: 'long', + }, + 'elasticsearch.server.gc.young.two': { + category: 'elasticsearch', + description: '', + example: '', + name: 'elasticsearch.server.gc.young.two', + type: 'long', + }, + 'elasticsearch.server.gc.overhead_seq': { + category: 'elasticsearch', + description: 'Sequence number', + example: 3449992, + name: 'elasticsearch.server.gc.overhead_seq', + type: 'long', + }, + 'elasticsearch.server.gc.collection_duration.ms': { + category: 'elasticsearch', + description: 'Time spent in GC, in milliseconds', + example: 1600, + name: 'elasticsearch.server.gc.collection_duration.ms', + type: 'float', + }, + 'elasticsearch.server.gc.observation_duration.ms': { + category: 'elasticsearch', + description: 'Total time over which collection was observed, in milliseconds', + example: 1800, + name: 'elasticsearch.server.gc.observation_duration.ms', + type: 'float', + }, + 'elasticsearch.slowlog.logger': { + category: 'elasticsearch', + description: 'Logger name', + example: 'index.search.slowlog.fetch', + name: 'elasticsearch.slowlog.logger', + type: 'keyword', + }, + 'elasticsearch.slowlog.took': { + category: 'elasticsearch', + description: 'Time it took to execute the query', + example: '300ms', + name: 'elasticsearch.slowlog.took', + type: 'keyword', + }, + 'elasticsearch.slowlog.types': { + category: 'elasticsearch', + description: 'Types', + example: '', + name: 'elasticsearch.slowlog.types', + type: 'keyword', + }, + 'elasticsearch.slowlog.stats': { + category: 'elasticsearch', + description: 'Stats groups', + example: 'group1', + name: 'elasticsearch.slowlog.stats', + type: 'keyword', + }, + 'elasticsearch.slowlog.search_type': { + category: 'elasticsearch', + description: 'Search type', + example: 'QUERY_THEN_FETCH', + name: 'elasticsearch.slowlog.search_type', + type: 'keyword', + }, + 'elasticsearch.slowlog.source_query': { + category: 'elasticsearch', + description: 'Slow query', + example: '{"query":{"match_all":{"boost":1.0}}}', + name: 'elasticsearch.slowlog.source_query', + type: 'keyword', + }, + 'elasticsearch.slowlog.extra_source': { + category: 'elasticsearch', + description: 'Extra source information', + example: '', + name: 'elasticsearch.slowlog.extra_source', + type: 'keyword', + }, + 'elasticsearch.slowlog.total_hits': { + category: 'elasticsearch', + description: 'Total hits', + example: 42, + name: 'elasticsearch.slowlog.total_hits', + type: 'keyword', + }, + 'elasticsearch.slowlog.total_shards': { + category: 'elasticsearch', + description: 'Total queried shards', + example: 22, + name: 'elasticsearch.slowlog.total_shards', + type: 'keyword', + }, + 'elasticsearch.slowlog.routing': { + category: 'elasticsearch', + description: 'Routing', + example: 's01HZ2QBk9jw4gtgaFtn', + name: 'elasticsearch.slowlog.routing', + type: 'keyword', + }, + 'elasticsearch.slowlog.id': { + category: 'elasticsearch', + description: 'Id', + example: '', + name: 'elasticsearch.slowlog.id', + type: 'keyword', + }, + 'elasticsearch.slowlog.type': { + category: 'elasticsearch', + description: 'Type', + example: 'doc', + name: 'elasticsearch.slowlog.type', + type: 'keyword', + }, + 'elasticsearch.slowlog.source': { + category: 'elasticsearch', + description: 'Source of document that was indexed', + name: 'elasticsearch.slowlog.source', + type: 'keyword', + }, + 'haproxy.frontend_name': { + category: 'haproxy', + description: 'Name of the frontend (or listener) which received and processed the connection.', + name: 'haproxy.frontend_name', + }, + 'haproxy.backend_name': { + category: 'haproxy', + description: + 'Name of the backend (or listener) which was selected to manage the connection to the server.', + name: 'haproxy.backend_name', + }, + 'haproxy.server_name': { + category: 'haproxy', + description: 'Name of the last server to which the connection was sent.', + name: 'haproxy.server_name', + }, + 'haproxy.total_waiting_time_ms': { + category: 'haproxy', + description: 'Total time in milliseconds spent waiting in the various queues', + name: 'haproxy.total_waiting_time_ms', + type: 'long', + }, + 'haproxy.connection_wait_time_ms': { + category: 'haproxy', + description: + 'Total time in milliseconds spent waiting for the connection to establish to the final server', + name: 'haproxy.connection_wait_time_ms', + type: 'long', + }, + 'haproxy.bytes_read': { + category: 'haproxy', + description: 'Total number of bytes transmitted to the client when the log is emitted.', + name: 'haproxy.bytes_read', + type: 'long', + }, + 'haproxy.time_queue': { + category: 'haproxy', + description: 'Total time in milliseconds spent waiting in the various queues.', + name: 'haproxy.time_queue', + type: 'long', + }, + 'haproxy.time_backend_connect': { + category: 'haproxy', + description: + 'Total time in milliseconds spent waiting for the connection to establish to the final server, including retries.', + name: 'haproxy.time_backend_connect', + type: 'long', + }, + 'haproxy.server_queue': { + category: 'haproxy', + description: + 'Total number of requests which were processed before this one in the server queue.', + name: 'haproxy.server_queue', + type: 'long', + }, + 'haproxy.backend_queue': { + category: 'haproxy', + description: + "Total number of requests which were processed before this one in the backend's global queue.", + name: 'haproxy.backend_queue', + type: 'long', + }, + 'haproxy.bind_name': { + category: 'haproxy', + description: 'Name of the listening address which received the connection.', + name: 'haproxy.bind_name', + }, + 'haproxy.error_message': { + category: 'haproxy', + description: 'Error message logged by HAProxy in case of error.', + name: 'haproxy.error_message', + type: 'text', + }, + 'haproxy.source': { + category: 'haproxy', + description: 'The HAProxy source of the log', + name: 'haproxy.source', + type: 'keyword', + }, + 'haproxy.termination_state': { + category: 'haproxy', + description: 'Condition the session was in when the session ended.', + name: 'haproxy.termination_state', + }, + 'haproxy.mode': { + category: 'haproxy', + description: 'mode that the frontend is operating (TCP or HTTP)', + name: 'haproxy.mode', + type: 'keyword', + }, + 'haproxy.connections.active': { + category: 'haproxy', + description: + 'Total number of concurrent connections on the process when the session was logged.', + name: 'haproxy.connections.active', + type: 'long', + }, + 'haproxy.connections.frontend': { + category: 'haproxy', + description: + 'Total number of concurrent connections on the frontend when the session was logged.', + name: 'haproxy.connections.frontend', + type: 'long', + }, + 'haproxy.connections.backend': { + category: 'haproxy', + description: + 'Total number of concurrent connections handled by the backend when the session was logged.', + name: 'haproxy.connections.backend', + type: 'long', + }, + 'haproxy.connections.server': { + category: 'haproxy', + description: + 'Total number of concurrent connections still active on the server when the session was logged.', + name: 'haproxy.connections.server', + type: 'long', + }, + 'haproxy.connections.retries': { + category: 'haproxy', + description: + 'Number of connection retries experienced by this session when trying to connect to the server.', + name: 'haproxy.connections.retries', + type: 'long', + }, + 'haproxy.client.ip': { + category: 'haproxy', + name: 'haproxy.client.ip', + type: 'alias', + }, + 'haproxy.client.port': { + category: 'haproxy', + name: 'haproxy.client.port', + type: 'alias', + }, + 'haproxy.process_name': { + category: 'haproxy', + name: 'haproxy.process_name', + type: 'alias', + }, + 'haproxy.pid': { + category: 'haproxy', + name: 'haproxy.pid', + type: 'alias', + }, + 'haproxy.destination.port': { + category: 'haproxy', + name: 'haproxy.destination.port', + type: 'alias', + }, + 'haproxy.destination.ip': { + category: 'haproxy', + name: 'haproxy.destination.ip', + type: 'alias', + }, + 'haproxy.geoip.continent_name': { + category: 'haproxy', + name: 'haproxy.geoip.continent_name', + type: 'alias', + }, + 'haproxy.geoip.country_iso_code': { + category: 'haproxy', + name: 'haproxy.geoip.country_iso_code', + type: 'alias', + }, + 'haproxy.geoip.location': { + category: 'haproxy', + name: 'haproxy.geoip.location', + type: 'alias', + }, + 'haproxy.geoip.region_name': { + category: 'haproxy', + name: 'haproxy.geoip.region_name', + type: 'alias', + }, + 'haproxy.geoip.city_name': { + category: 'haproxy', + name: 'haproxy.geoip.city_name', + type: 'alias', + }, + 'haproxy.geoip.region_iso_code': { + category: 'haproxy', + name: 'haproxy.geoip.region_iso_code', + type: 'alias', + }, + 'haproxy.http.response.captured_cookie': { + category: 'haproxy', + description: + 'Optional "name=value" entry indicating that the client had this cookie in the response. ', + name: 'haproxy.http.response.captured_cookie', + }, + 'haproxy.http.response.captured_headers': { + category: 'haproxy', + description: + 'List of headers captured in the response due to the presence of the "capture response header" statement in the frontend. ', + name: 'haproxy.http.response.captured_headers', + type: 'keyword', + }, + 'haproxy.http.response.status_code': { + category: 'haproxy', + name: 'haproxy.http.response.status_code', + type: 'alias', + }, + 'haproxy.http.request.captured_cookie': { + category: 'haproxy', + description: + 'Optional "name=value" entry indicating that the server has returned a cookie with its request. ', + name: 'haproxy.http.request.captured_cookie', + }, + 'haproxy.http.request.captured_headers': { + category: 'haproxy', + description: + 'List of headers captured in the request due to the presence of the "capture request header" statement in the frontend. ', + name: 'haproxy.http.request.captured_headers', + type: 'keyword', + }, + 'haproxy.http.request.raw_request_line': { + category: 'haproxy', + description: + 'Complete HTTP request line, including the method, request and HTTP version string.', + name: 'haproxy.http.request.raw_request_line', + type: 'keyword', + }, + 'haproxy.http.request.time_wait_without_data_ms': { + category: 'haproxy', + description: + 'Total time in milliseconds spent waiting for the server to send a full HTTP response, not counting data.', + name: 'haproxy.http.request.time_wait_without_data_ms', + type: 'long', + }, + 'haproxy.http.request.time_wait_ms': { + category: 'haproxy', + description: + 'Total time in milliseconds spent waiting for a full HTTP request from the client (not counting body) after the first byte was received.', + name: 'haproxy.http.request.time_wait_ms', + type: 'long', + }, + 'haproxy.tcp.connection_waiting_time_ms': { + category: 'haproxy', + description: 'Total time in milliseconds elapsed between the accept and the last close', + name: 'haproxy.tcp.connection_waiting_time_ms', + type: 'long', + }, + 'icinga.debug.facility': { + category: 'icinga', + description: 'Specifies what component of Icinga logged the message. ', + name: 'icinga.debug.facility', + type: 'keyword', + }, + 'icinga.debug.severity': { + category: 'icinga', + name: 'icinga.debug.severity', + type: 'alias', + }, + 'icinga.debug.message': { + category: 'icinga', + name: 'icinga.debug.message', + type: 'alias', + }, + 'icinga.main.facility': { + category: 'icinga', + description: 'Specifies what component of Icinga logged the message. ', + name: 'icinga.main.facility', + type: 'keyword', + }, + 'icinga.main.severity': { + category: 'icinga', + name: 'icinga.main.severity', + type: 'alias', + }, + 'icinga.main.message': { + category: 'icinga', + name: 'icinga.main.message', + type: 'alias', + }, + 'icinga.startup.facility': { + category: 'icinga', + description: 'Specifies what component of Icinga logged the message. ', + name: 'icinga.startup.facility', + type: 'keyword', + }, + 'icinga.startup.severity': { + category: 'icinga', + name: 'icinga.startup.severity', + type: 'alias', + }, + 'icinga.startup.message': { + category: 'icinga', + name: 'icinga.startup.message', + type: 'alias', + }, + 'iis.access.sub_status': { + category: 'iis', + description: 'The HTTP substatus code. ', + name: 'iis.access.sub_status', + type: 'long', + }, + 'iis.access.win32_status': { + category: 'iis', + description: 'The Windows status code. ', + name: 'iis.access.win32_status', + type: 'long', + }, + 'iis.access.site_name': { + category: 'iis', + description: 'The site name and instance number. ', + name: 'iis.access.site_name', + type: 'keyword', + }, + 'iis.access.server_name': { + category: 'iis', + description: 'The name of the server on which the log file entry was generated. ', + name: 'iis.access.server_name', + type: 'keyword', + }, + 'iis.access.cookie': { + category: 'iis', + description: 'The content of the cookie sent or received, if any. ', + name: 'iis.access.cookie', + type: 'keyword', + }, + 'iis.access.body_received.bytes': { + category: 'iis', + name: 'iis.access.body_received.bytes', + type: 'alias', + }, + 'iis.access.body_sent.bytes': { + category: 'iis', + name: 'iis.access.body_sent.bytes', + type: 'alias', + }, + 'iis.access.server_ip': { + category: 'iis', + name: 'iis.access.server_ip', + type: 'alias', + }, + 'iis.access.method': { + category: 'iis', + name: 'iis.access.method', + type: 'alias', + }, + 'iis.access.url': { + category: 'iis', + name: 'iis.access.url', + type: 'alias', + }, + 'iis.access.query_string': { + category: 'iis', + name: 'iis.access.query_string', + type: 'alias', + }, + 'iis.access.port': { + category: 'iis', + name: 'iis.access.port', + type: 'alias', + }, + 'iis.access.user_name': { + category: 'iis', + name: 'iis.access.user_name', + type: 'alias', + }, + 'iis.access.remote_ip': { + category: 'iis', + name: 'iis.access.remote_ip', + type: 'alias', + }, + 'iis.access.referrer': { + category: 'iis', + name: 'iis.access.referrer', + type: 'alias', + }, + 'iis.access.response_code': { + category: 'iis', + name: 'iis.access.response_code', + type: 'alias', + }, + 'iis.access.http_version': { + category: 'iis', + name: 'iis.access.http_version', + type: 'alias', + }, + 'iis.access.hostname': { + category: 'iis', + name: 'iis.access.hostname', + type: 'alias', + }, + 'iis.access.user_agent.device': { + category: 'iis', + name: 'iis.access.user_agent.device', + type: 'alias', + }, + 'iis.access.user_agent.name': { + category: 'iis', + name: 'iis.access.user_agent.name', + type: 'alias', + }, + 'iis.access.user_agent.os': { + category: 'iis', + name: 'iis.access.user_agent.os', + type: 'alias', + }, + 'iis.access.user_agent.os_name': { + category: 'iis', + name: 'iis.access.user_agent.os_name', + type: 'alias', + }, + 'iis.access.user_agent.original': { + category: 'iis', + name: 'iis.access.user_agent.original', + type: 'alias', + }, + 'iis.access.geoip.continent_name': { + category: 'iis', + name: 'iis.access.geoip.continent_name', + type: 'alias', + }, + 'iis.access.geoip.country_iso_code': { + category: 'iis', + name: 'iis.access.geoip.country_iso_code', + type: 'alias', + }, + 'iis.access.geoip.location': { + category: 'iis', + name: 'iis.access.geoip.location', + type: 'alias', + }, + 'iis.access.geoip.region_name': { + category: 'iis', + name: 'iis.access.geoip.region_name', + type: 'alias', + }, + 'iis.access.geoip.city_name': { + category: 'iis', + name: 'iis.access.geoip.city_name', + type: 'alias', + }, + 'iis.access.geoip.region_iso_code': { + category: 'iis', + name: 'iis.access.geoip.region_iso_code', + type: 'alias', + }, + 'iis.error.reason_phrase': { + category: 'iis', + description: 'The HTTP reason phrase. ', + name: 'iis.error.reason_phrase', + type: 'keyword', + }, + 'iis.error.queue_name': { + category: 'iis', + description: 'The IIS application pool name. ', + name: 'iis.error.queue_name', + type: 'keyword', + }, + 'iis.error.remote_ip': { + category: 'iis', + name: 'iis.error.remote_ip', + type: 'alias', + }, + 'iis.error.remote_port': { + category: 'iis', + name: 'iis.error.remote_port', + type: 'alias', + }, + 'iis.error.server_ip': { + category: 'iis', + name: 'iis.error.server_ip', + type: 'alias', + }, + 'iis.error.server_port': { + category: 'iis', + name: 'iis.error.server_port', + type: 'alias', + }, + 'iis.error.http_version': { + category: 'iis', + name: 'iis.error.http_version', + type: 'alias', + }, + 'iis.error.method': { + category: 'iis', + name: 'iis.error.method', + type: 'alias', + }, + 'iis.error.url': { + category: 'iis', + name: 'iis.error.url', + type: 'alias', + }, + 'iis.error.response_code': { + category: 'iis', + name: 'iis.error.response_code', + type: 'alias', + }, + 'iis.error.geoip.continent_name': { + category: 'iis', + name: 'iis.error.geoip.continent_name', + type: 'alias', + }, + 'iis.error.geoip.country_iso_code': { + category: 'iis', + name: 'iis.error.geoip.country_iso_code', + type: 'alias', + }, + 'iis.error.geoip.location': { + category: 'iis', + name: 'iis.error.geoip.location', + type: 'alias', + }, + 'iis.error.geoip.region_name': { + category: 'iis', + name: 'iis.error.geoip.region_name', + type: 'alias', + }, + 'iis.error.geoip.city_name': { + category: 'iis', + name: 'iis.error.geoip.city_name', + type: 'alias', + }, + 'iis.error.geoip.region_iso_code': { + category: 'iis', + name: 'iis.error.geoip.region_iso_code', + type: 'alias', + }, + 'kafka.log.level': { + category: 'kafka', + name: 'kafka.log.level', + type: 'alias', + }, + 'kafka.log.message': { + category: 'kafka', + name: 'kafka.log.message', + type: 'alias', + }, + 'kafka.log.component': { + category: 'kafka', + description: 'Component the log is coming from. ', + name: 'kafka.log.component', + type: 'keyword', + }, + 'kafka.log.class': { + category: 'kafka', + description: 'Java class the log is coming from. ', + name: 'kafka.log.class', + type: 'keyword', + }, + 'kafka.log.thread': { + category: 'kafka', + description: 'Thread name the log is coming from. ', + name: 'kafka.log.thread', + type: 'keyword', + }, + 'kafka.log.trace.class': { + category: 'kafka', + description: 'Java class the trace is coming from. ', + name: 'kafka.log.trace.class', + type: 'keyword', + }, + 'kafka.log.trace.message': { + category: 'kafka', + description: 'Message part of the trace. ', + name: 'kafka.log.trace.message', + type: 'text', + }, + 'kibana.log.tags': { + category: 'kibana', + description: 'Kibana logging tags. ', + name: 'kibana.log.tags', + type: 'keyword', + }, + 'kibana.log.state': { + category: 'kibana', + description: 'Current state of Kibana. ', + name: 'kibana.log.state', + type: 'keyword', + }, + 'kibana.log.meta': { + category: 'kibana', + name: 'kibana.log.meta', + type: 'object', + }, + 'kibana.log.kibana.log.meta.req.headers.referer': { + category: 'kibana', + name: 'kibana.log.kibana.log.meta.req.headers.referer', + type: 'alias', + }, + 'kibana.log.kibana.log.meta.req.referer': { + category: 'kibana', + name: 'kibana.log.kibana.log.meta.req.referer', + type: 'alias', + }, + 'kibana.log.kibana.log.meta.req.headers.user-agent': { + category: 'kibana', + name: 'kibana.log.kibana.log.meta.req.headers.user-agent', + type: 'alias', + }, + 'kibana.log.kibana.log.meta.req.remoteAddress': { + category: 'kibana', + name: 'kibana.log.kibana.log.meta.req.remoteAddress', + type: 'alias', + }, + 'kibana.log.kibana.log.meta.req.url': { + category: 'kibana', + name: 'kibana.log.kibana.log.meta.req.url', + type: 'alias', + }, + 'kibana.log.kibana.log.meta.statusCode': { + category: 'kibana', + name: 'kibana.log.kibana.log.meta.statusCode', + type: 'alias', + }, + 'kibana.log.kibana.log.meta.method': { + category: 'kibana', + name: 'kibana.log.kibana.log.meta.method', + type: 'alias', + }, + 'logstash.log.module': { + category: 'logstash', + description: 'The module or class where the event originate. ', + name: 'logstash.log.module', + type: 'keyword', + }, + 'logstash.log.thread': { + category: 'logstash', + description: 'Information about the running thread where the log originate. ', + name: 'logstash.log.thread', + type: 'keyword', + }, + 'logstash.log.log_event': { + category: 'logstash', + description: 'key and value debugging information. ', + name: 'logstash.log.log_event', + type: 'object', + }, + 'logstash.log.pipeline_id': { + category: 'logstash', + description: 'The ID of the pipeline. ', + example: 'main', + name: 'logstash.log.pipeline_id', + type: 'keyword', + }, + 'logstash.log.message': { + category: 'logstash', + name: 'logstash.log.message', + type: 'alias', + }, + 'logstash.log.level': { + category: 'logstash', + name: 'logstash.log.level', + type: 'alias', + }, + 'logstash.slowlog.module': { + category: 'logstash', + description: 'The module or class where the event originate. ', + name: 'logstash.slowlog.module', + type: 'keyword', + }, + 'logstash.slowlog.thread': { + category: 'logstash', + description: 'Information about the running thread where the log originate. ', + name: 'logstash.slowlog.thread', + type: 'keyword', + }, + 'logstash.slowlog.event': { + category: 'logstash', + description: 'Raw dump of the original event ', + name: 'logstash.slowlog.event', + type: 'keyword', + }, + 'logstash.slowlog.plugin_name': { + category: 'logstash', + description: 'Name of the plugin ', + name: 'logstash.slowlog.plugin_name', + type: 'keyword', + }, + 'logstash.slowlog.plugin_type': { + category: 'logstash', + description: 'Type of the plugin: Inputs, Filters, Outputs or Codecs. ', + name: 'logstash.slowlog.plugin_type', + type: 'keyword', + }, + 'logstash.slowlog.took_in_millis': { + category: 'logstash', + description: 'Execution time for the plugin in milliseconds. ', + name: 'logstash.slowlog.took_in_millis', + type: 'long', + }, + 'logstash.slowlog.plugin_params': { + category: 'logstash', + description: 'String value of the plugin configuration ', + name: 'logstash.slowlog.plugin_params', + type: 'keyword', + }, + 'logstash.slowlog.plugin_params_object': { + category: 'logstash', + description: 'key -> value of the configuration used by the plugin. ', + name: 'logstash.slowlog.plugin_params_object', + type: 'object', + }, + 'logstash.slowlog.level': { + category: 'logstash', + name: 'logstash.slowlog.level', + type: 'alias', + }, + 'logstash.slowlog.took_in_nanos': { + category: 'logstash', + name: 'logstash.slowlog.took_in_nanos', + type: 'alias', + }, + 'mongodb.log.component': { + category: 'mongodb', + description: 'Functional categorization of message ', + example: 'COMMAND', + name: 'mongodb.log.component', + type: 'keyword', + }, + 'mongodb.log.context': { + category: 'mongodb', + description: 'Context of message ', + example: 'initandlisten', + name: 'mongodb.log.context', + type: 'keyword', + }, + 'mongodb.log.severity': { + category: 'mongodb', + name: 'mongodb.log.severity', + type: 'alias', + }, + 'mongodb.log.message': { + category: 'mongodb', + name: 'mongodb.log.message', + type: 'alias', + }, + 'mysql.thread_id': { + category: 'mysql', + description: 'The connection or thread ID for the query. ', + name: 'mysql.thread_id', + type: 'long', + }, + 'mysql.error.thread_id': { + category: 'mysql', + name: 'mysql.error.thread_id', + type: 'alias', + }, + 'mysql.error.level': { + category: 'mysql', + name: 'mysql.error.level', + type: 'alias', + }, + 'mysql.error.message': { + category: 'mysql', + name: 'mysql.error.message', + type: 'alias', + }, + 'mysql.slowlog.lock_time.sec': { + category: 'mysql', + description: + 'The amount of time the query waited for the lock to be available. The value is in seconds, as a floating point number. ', + name: 'mysql.slowlog.lock_time.sec', + type: 'float', + }, + 'mysql.slowlog.rows_sent': { + category: 'mysql', + description: 'The number of rows returned by the query. ', + name: 'mysql.slowlog.rows_sent', + type: 'long', + }, + 'mysql.slowlog.rows_examined': { + category: 'mysql', + description: 'The number of rows scanned by the query. ', + name: 'mysql.slowlog.rows_examined', + type: 'long', + }, + 'mysql.slowlog.rows_affected': { + category: 'mysql', + description: 'The number of rows modified by the query. ', + name: 'mysql.slowlog.rows_affected', + type: 'long', + }, + 'mysql.slowlog.bytes_sent': { + category: 'mysql', + description: 'The number of bytes sent to client. ', + name: 'mysql.slowlog.bytes_sent', + type: 'long', + format: 'bytes', + }, + 'mysql.slowlog.bytes_received': { + category: 'mysql', + description: 'The number of bytes received from client. ', + name: 'mysql.slowlog.bytes_received', + type: 'long', + format: 'bytes', + }, + 'mysql.slowlog.query': { + category: 'mysql', + description: 'The slow query. ', + name: 'mysql.slowlog.query', + }, + 'mysql.slowlog.id': { + category: 'mysql', + name: 'mysql.slowlog.id', + type: 'alias', + }, + 'mysql.slowlog.schema': { + category: 'mysql', + description: 'The schema where the slow query was executed. ', + name: 'mysql.slowlog.schema', + type: 'keyword', + }, + 'mysql.slowlog.current_user': { + category: 'mysql', + description: + 'Current authenticated user, used to determine access privileges. Can differ from the value for user. ', + name: 'mysql.slowlog.current_user', + type: 'keyword', + }, + 'mysql.slowlog.last_errno': { + category: 'mysql', + description: 'Last SQL error seen. ', + name: 'mysql.slowlog.last_errno', + type: 'keyword', + }, + 'mysql.slowlog.killed': { + category: 'mysql', + description: 'Code of the reason if the query was killed. ', + name: 'mysql.slowlog.killed', + type: 'keyword', + }, + 'mysql.slowlog.query_cache_hit': { + category: 'mysql', + description: 'Whether the query cache was hit. ', + name: 'mysql.slowlog.query_cache_hit', + type: 'boolean', + }, + 'mysql.slowlog.tmp_table': { + category: 'mysql', + description: 'Whether a temporary table was used to resolve the query. ', + name: 'mysql.slowlog.tmp_table', + type: 'boolean', + }, + 'mysql.slowlog.tmp_table_on_disk': { + category: 'mysql', + description: 'Whether the query needed temporary tables on disk. ', + name: 'mysql.slowlog.tmp_table_on_disk', + type: 'boolean', + }, + 'mysql.slowlog.tmp_tables': { + category: 'mysql', + description: 'Number of temporary tables created for this query ', + name: 'mysql.slowlog.tmp_tables', + type: 'long', + }, + 'mysql.slowlog.tmp_disk_tables': { + category: 'mysql', + description: 'Number of temporary tables created on disk for this query. ', + name: 'mysql.slowlog.tmp_disk_tables', + type: 'long', + }, + 'mysql.slowlog.tmp_table_sizes': { + category: 'mysql', + description: 'Size of temporary tables created for this query.', + name: 'mysql.slowlog.tmp_table_sizes', + type: 'long', + format: 'bytes', + }, + 'mysql.slowlog.filesort': { + category: 'mysql', + description: 'Whether filesort optimization was used. ', + name: 'mysql.slowlog.filesort', + type: 'boolean', + }, + 'mysql.slowlog.filesort_on_disk': { + category: 'mysql', + description: 'Whether filesort optimization was used and it needed temporary tables on disk. ', + name: 'mysql.slowlog.filesort_on_disk', + type: 'boolean', + }, + 'mysql.slowlog.priority_queue': { + category: 'mysql', + description: 'Whether a priority queue was used for filesort. ', + name: 'mysql.slowlog.priority_queue', + type: 'boolean', + }, + 'mysql.slowlog.full_scan': { + category: 'mysql', + description: 'Whether a full table scan was needed for the slow query. ', + name: 'mysql.slowlog.full_scan', + type: 'boolean', + }, + 'mysql.slowlog.full_join': { + category: 'mysql', + description: + 'Whether a full join was needed for the slow query (no indexes were used for joins). ', + name: 'mysql.slowlog.full_join', + type: 'boolean', + }, + 'mysql.slowlog.merge_passes': { + category: 'mysql', + description: 'Number of merge passes executed for the query. ', + name: 'mysql.slowlog.merge_passes', + type: 'long', + }, + 'mysql.slowlog.sort_merge_passes': { + category: 'mysql', + description: 'Number of merge passes that the sort algorithm has had to do. ', + name: 'mysql.slowlog.sort_merge_passes', + type: 'long', + }, + 'mysql.slowlog.sort_range_count': { + category: 'mysql', + description: 'Number of sorts that were done using ranges. ', + name: 'mysql.slowlog.sort_range_count', + type: 'long', + }, + 'mysql.slowlog.sort_rows': { + category: 'mysql', + description: 'Number of sorted rows. ', + name: 'mysql.slowlog.sort_rows', + type: 'long', + }, + 'mysql.slowlog.sort_scan_count': { + category: 'mysql', + description: 'Number of sorts that were done by scanning the table. ', + name: 'mysql.slowlog.sort_scan_count', + type: 'long', + }, + 'mysql.slowlog.log_slow_rate_type': { + category: 'mysql', + description: + 'Type of slow log rate limit, it can be `session` if the rate limit is applied per session, or `query` if it applies per query. ', + name: 'mysql.slowlog.log_slow_rate_type', + type: 'keyword', + }, + 'mysql.slowlog.log_slow_rate_limit': { + category: 'mysql', + description: + 'Slow log rate limit, a value of 100 means that one in a hundred queries or sessions are being logged. ', + name: 'mysql.slowlog.log_slow_rate_limit', + type: 'keyword', + }, + 'mysql.slowlog.read_first': { + category: 'mysql', + description: 'The number of times the first entry in an index was read. ', + name: 'mysql.slowlog.read_first', + type: 'long', + }, + 'mysql.slowlog.read_last': { + category: 'mysql', + description: 'The number of times the last key in an index was read. ', + name: 'mysql.slowlog.read_last', + type: 'long', + }, + 'mysql.slowlog.read_key': { + category: 'mysql', + description: 'The number of requests to read a row based on a key. ', + name: 'mysql.slowlog.read_key', + type: 'long', + }, + 'mysql.slowlog.read_next': { + category: 'mysql', + description: 'The number of requests to read the next row in key order. ', + name: 'mysql.slowlog.read_next', + type: 'long', + }, + 'mysql.slowlog.read_prev': { + category: 'mysql', + description: 'The number of requests to read the previous row in key order. ', + name: 'mysql.slowlog.read_prev', + type: 'long', + }, + 'mysql.slowlog.read_rnd': { + category: 'mysql', + description: 'The number of requests to read a row based on a fixed position. ', + name: 'mysql.slowlog.read_rnd', + type: 'long', + }, + 'mysql.slowlog.read_rnd_next': { + category: 'mysql', + description: 'The number of requests to read the next row in the data file. ', + name: 'mysql.slowlog.read_rnd_next', + type: 'long', + }, + 'mysql.slowlog.innodb.trx_id': { + category: 'mysql', + description: 'Transaction ID ', + name: 'mysql.slowlog.innodb.trx_id', + type: 'keyword', + }, + 'mysql.slowlog.innodb.io_r_ops': { + category: 'mysql', + description: 'Number of page read operations. ', + name: 'mysql.slowlog.innodb.io_r_ops', + type: 'long', + }, + 'mysql.slowlog.innodb.io_r_bytes': { + category: 'mysql', + description: 'Bytes read during page read operations. ', + name: 'mysql.slowlog.innodb.io_r_bytes', + type: 'long', + format: 'bytes', + }, + 'mysql.slowlog.innodb.io_r_wait.sec': { + category: 'mysql', + description: 'How long it took to read all needed data from storage. ', + name: 'mysql.slowlog.innodb.io_r_wait.sec', + type: 'long', + }, + 'mysql.slowlog.innodb.rec_lock_wait.sec': { + category: 'mysql', + description: 'How long the query waited for locks. ', + name: 'mysql.slowlog.innodb.rec_lock_wait.sec', + type: 'long', + }, + 'mysql.slowlog.innodb.queue_wait.sec': { + category: 'mysql', + description: + 'How long the query waited to enter the InnoDB queue and to be executed once in the queue. ', + name: 'mysql.slowlog.innodb.queue_wait.sec', + type: 'long', + }, + 'mysql.slowlog.innodb.pages_distinct': { + category: 'mysql', + description: 'Approximated count of pages accessed to execute the query. ', + name: 'mysql.slowlog.innodb.pages_distinct', + type: 'long', + }, + 'mysql.slowlog.user': { + category: 'mysql', + name: 'mysql.slowlog.user', + type: 'alias', + }, + 'mysql.slowlog.host': { + category: 'mysql', + name: 'mysql.slowlog.host', + type: 'alias', + }, + 'mysql.slowlog.ip': { + category: 'mysql', + name: 'mysql.slowlog.ip', + type: 'alias', + }, + 'nats.log.client.id': { + category: 'nats', + description: 'The id of the client ', + name: 'nats.log.client.id', + type: 'integer', + }, + 'nats.log.msg.bytes': { + category: 'nats', + description: 'Size of the payload in bytes ', + name: 'nats.log.msg.bytes', + type: 'long', + format: 'bytes', + }, + 'nats.log.msg.type': { + category: 'nats', + description: 'The protocol message type ', + name: 'nats.log.msg.type', + type: 'keyword', + }, + 'nats.log.msg.subject': { + category: 'nats', + description: 'Subject name this message was received on ', + name: 'nats.log.msg.subject', + type: 'keyword', + }, + 'nats.log.msg.sid': { + category: 'nats', + description: 'The unique alphanumeric subscription ID of the subject ', + name: 'nats.log.msg.sid', + type: 'integer', + }, + 'nats.log.msg.reply_to': { + category: 'nats', + description: 'The inbox subject on which the publisher is listening for responses ', + name: 'nats.log.msg.reply_to', + type: 'keyword', + }, + 'nats.log.msg.max_messages': { + category: 'nats', + description: 'An optional number of messages to wait for before automatically unsubscribing ', + name: 'nats.log.msg.max_messages', + type: 'integer', + }, + 'nats.log.msg.error.message': { + category: 'nats', + description: 'Details about the error occurred ', + name: 'nats.log.msg.error.message', + type: 'text', + }, + 'nats.log.msg.queue_group': { + category: 'nats', + description: 'The queue group which subscriber will join ', + name: 'nats.log.msg.queue_group', + type: 'text', + }, + 'nginx.access.remote_ip_list': { + category: 'nginx', + description: + 'An array of remote IP addresses. It is a list because it is common to include, besides the client IP address, IP addresses from headers like `X-Forwarded-For`. Real source IP is restored to `source.ip`. ', + name: 'nginx.access.remote_ip_list', + type: 'array', + }, + 'nginx.access.body_sent.bytes': { + category: 'nginx', + name: 'nginx.access.body_sent.bytes', + type: 'alias', + }, + 'nginx.access.user_name': { + category: 'nginx', + name: 'nginx.access.user_name', + type: 'alias', + }, + 'nginx.access.method': { + category: 'nginx', + name: 'nginx.access.method', + type: 'alias', + }, + 'nginx.access.url': { + category: 'nginx', + name: 'nginx.access.url', + type: 'alias', + }, + 'nginx.access.http_version': { + category: 'nginx', + name: 'nginx.access.http_version', + type: 'alias', + }, + 'nginx.access.response_code': { + category: 'nginx', + name: 'nginx.access.response_code', + type: 'alias', + }, + 'nginx.access.referrer': { + category: 'nginx', + name: 'nginx.access.referrer', + type: 'alias', + }, + 'nginx.access.agent': { + category: 'nginx', + name: 'nginx.access.agent', + type: 'alias', + }, + 'nginx.access.user_agent.device': { + category: 'nginx', + name: 'nginx.access.user_agent.device', + type: 'alias', + }, + 'nginx.access.user_agent.name': { + category: 'nginx', + name: 'nginx.access.user_agent.name', + type: 'alias', + }, + 'nginx.access.user_agent.os': { + category: 'nginx', + name: 'nginx.access.user_agent.os', + type: 'alias', + }, + 'nginx.access.user_agent.os_name': { + category: 'nginx', + name: 'nginx.access.user_agent.os_name', + type: 'alias', + }, + 'nginx.access.user_agent.original': { + category: 'nginx', + name: 'nginx.access.user_agent.original', + type: 'alias', + }, + 'nginx.access.geoip.continent_name': { + category: 'nginx', + name: 'nginx.access.geoip.continent_name', + type: 'alias', + }, + 'nginx.access.geoip.country_iso_code': { + category: 'nginx', + name: 'nginx.access.geoip.country_iso_code', + type: 'alias', + }, + 'nginx.access.geoip.location': { + category: 'nginx', + name: 'nginx.access.geoip.location', + type: 'alias', + }, + 'nginx.access.geoip.region_name': { + category: 'nginx', + name: 'nginx.access.geoip.region_name', + type: 'alias', + }, + 'nginx.access.geoip.city_name': { + category: 'nginx', + name: 'nginx.access.geoip.city_name', + type: 'alias', + }, + 'nginx.access.geoip.region_iso_code': { + category: 'nginx', + name: 'nginx.access.geoip.region_iso_code', + type: 'alias', + }, + 'nginx.error.connection_id': { + category: 'nginx', + description: 'Connection identifier. ', + name: 'nginx.error.connection_id', + type: 'long', + }, + 'nginx.error.level': { + category: 'nginx', + name: 'nginx.error.level', + type: 'alias', + }, + 'nginx.error.pid': { + category: 'nginx', + name: 'nginx.error.pid', + type: 'alias', + }, + 'nginx.error.tid': { + category: 'nginx', + name: 'nginx.error.tid', + type: 'alias', + }, + 'nginx.error.message': { + category: 'nginx', + name: 'nginx.error.message', + type: 'alias', + }, + 'nginx.ingress_controller.remote_ip_list': { + category: 'nginx', + description: + 'An array of remote IP addresses. It is a list because it is common to include, besides the client IP address, IP addresses from headers like `X-Forwarded-For`. Real source IP is restored to `source.ip`. ', + name: 'nginx.ingress_controller.remote_ip_list', + type: 'array', + }, + 'nginx.ingress_controller.http.request.length': { + category: 'nginx', + description: 'The request length (including request line, header, and request body) ', + name: 'nginx.ingress_controller.http.request.length', + type: 'long', + format: 'bytes', + }, + 'nginx.ingress_controller.http.request.time': { + category: 'nginx', + description: 'Time elapsed since the first bytes were read from the client ', + name: 'nginx.ingress_controller.http.request.time', + type: 'double', + format: 'duration', + }, + 'nginx.ingress_controller.upstream.name': { + category: 'nginx', + description: 'The name of the upstream. ', + name: 'nginx.ingress_controller.upstream.name', + type: 'keyword', + }, + 'nginx.ingress_controller.upstream.alternative_name': { + category: 'nginx', + description: 'The name of the alternative upstream. ', + name: 'nginx.ingress_controller.upstream.alternative_name', + type: 'keyword', + }, + 'nginx.ingress_controller.upstream.response.length': { + category: 'nginx', + description: 'The length of the response obtained from the upstream server ', + name: 'nginx.ingress_controller.upstream.response.length', + type: 'long', + format: 'bytes', + }, + 'nginx.ingress_controller.upstream.response.time': { + category: 'nginx', + description: + 'The time spent on receiving the response from the upstream server as seconds with millisecond resolution ', + name: 'nginx.ingress_controller.upstream.response.time', + type: 'double', + format: 'duration', + }, + 'nginx.ingress_controller.upstream.response.status_code': { + category: 'nginx', + description: 'The status code of the response obtained from the upstream server ', + name: 'nginx.ingress_controller.upstream.response.status_code', + type: 'long', + }, + 'nginx.ingress_controller.http.request.id': { + category: 'nginx', + description: 'The randomly generated ID of the request ', + name: 'nginx.ingress_controller.http.request.id', + type: 'keyword', + }, + 'nginx.ingress_controller.upstream.ip': { + category: 'nginx', + description: + 'The IP address of the upstream server. If several servers were contacted during request processing, their addresses are separated by commas. ', + name: 'nginx.ingress_controller.upstream.ip', + type: 'ip', + }, + 'nginx.ingress_controller.upstream.port': { + category: 'nginx', + description: 'The port of the upstream server. ', + name: 'nginx.ingress_controller.upstream.port', + type: 'long', + }, + 'nginx.ingress_controller.body_sent.bytes': { + category: 'nginx', + name: 'nginx.ingress_controller.body_sent.bytes', + type: 'alias', + }, + 'nginx.ingress_controller.user_name': { + category: 'nginx', + name: 'nginx.ingress_controller.user_name', + type: 'alias', + }, + 'nginx.ingress_controller.method': { + category: 'nginx', + name: 'nginx.ingress_controller.method', + type: 'alias', + }, + 'nginx.ingress_controller.url': { + category: 'nginx', + name: 'nginx.ingress_controller.url', + type: 'alias', + }, + 'nginx.ingress_controller.http_version': { + category: 'nginx', + name: 'nginx.ingress_controller.http_version', + type: 'alias', + }, + 'nginx.ingress_controller.response_code': { + category: 'nginx', + name: 'nginx.ingress_controller.response_code', + type: 'alias', + }, + 'nginx.ingress_controller.referrer': { + category: 'nginx', + name: 'nginx.ingress_controller.referrer', + type: 'alias', + }, + 'nginx.ingress_controller.agent': { + category: 'nginx', + name: 'nginx.ingress_controller.agent', + type: 'alias', + }, + 'nginx.ingress_controller.user_agent.device': { + category: 'nginx', + name: 'nginx.ingress_controller.user_agent.device', + type: 'alias', + }, + 'nginx.ingress_controller.user_agent.name': { + category: 'nginx', + name: 'nginx.ingress_controller.user_agent.name', + type: 'alias', + }, + 'nginx.ingress_controller.user_agent.os': { + category: 'nginx', + name: 'nginx.ingress_controller.user_agent.os', + type: 'alias', + }, + 'nginx.ingress_controller.user_agent.os_name': { + category: 'nginx', + name: 'nginx.ingress_controller.user_agent.os_name', + type: 'alias', + }, + 'nginx.ingress_controller.user_agent.original': { + category: 'nginx', + name: 'nginx.ingress_controller.user_agent.original', + type: 'alias', + }, + 'nginx.ingress_controller.geoip.continent_name': { + category: 'nginx', + name: 'nginx.ingress_controller.geoip.continent_name', + type: 'alias', + }, + 'nginx.ingress_controller.geoip.country_iso_code': { + category: 'nginx', + name: 'nginx.ingress_controller.geoip.country_iso_code', + type: 'alias', + }, + 'nginx.ingress_controller.geoip.location': { + category: 'nginx', + name: 'nginx.ingress_controller.geoip.location', + type: 'alias', + }, + 'nginx.ingress_controller.geoip.region_name': { + category: 'nginx', + name: 'nginx.ingress_controller.geoip.region_name', + type: 'alias', + }, + 'nginx.ingress_controller.geoip.city_name': { + category: 'nginx', + name: 'nginx.ingress_controller.geoip.city_name', + type: 'alias', + }, + 'nginx.ingress_controller.geoip.region_iso_code': { + category: 'nginx', + name: 'nginx.ingress_controller.geoip.region_iso_code', + type: 'alias', + }, + 'osquery.result.name': { + category: 'osquery', + description: 'The name of the query that generated this event. ', + name: 'osquery.result.name', + type: 'keyword', + }, + 'osquery.result.action': { + category: 'osquery', + description: + 'For incremental data, marks whether the entry was added or removed. It can be one of "added", "removed", or "snapshot". ', + name: 'osquery.result.action', + type: 'keyword', + }, + 'osquery.result.host_identifier': { + category: 'osquery', + description: + 'The identifier for the host on which the osquery agent is running. Normally the hostname. ', + name: 'osquery.result.host_identifier', + type: 'keyword', + }, + 'osquery.result.unix_time': { + category: 'osquery', + description: + 'Unix timestamp of the event, in seconds since the epoch. Used for computing the `@timestamp` column. ', + name: 'osquery.result.unix_time', + type: 'long', + }, + 'osquery.result.calendar_time': { + category: 'osquery', + description: 'String representation of the collection time, as formatted by osquery. ', + name: 'osquery.result.calendar_time', + type: 'keyword', + }, + 'postgresql.log.timestamp': { + category: 'postgresql', + description: 'The timestamp from the log line. ', + name: 'postgresql.log.timestamp', + }, + 'postgresql.log.core_id': { + category: 'postgresql', + description: 'Core id ', + name: 'postgresql.log.core_id', + type: 'long', + }, + 'postgresql.log.database': { + category: 'postgresql', + description: 'Name of database ', + example: 'mydb', + name: 'postgresql.log.database', + }, + 'postgresql.log.query': { + category: 'postgresql', + description: 'Query statement. ', + example: 'SELECT * FROM users;', + name: 'postgresql.log.query', + }, + 'postgresql.log.query_step': { + category: 'postgresql', + description: + 'Statement step when using extended query protocol (one of statement, parse, bind or execute) ', + example: 'parse', + name: 'postgresql.log.query_step', + }, + 'postgresql.log.query_name': { + category: 'postgresql', + description: + 'Name given to a query when using extended query protocol. If it is "", or not present, this field is ignored. ', + example: 'pdo_stmt_00000001', + name: 'postgresql.log.query_name', + }, + 'postgresql.log.error.code': { + category: 'postgresql', + description: 'Error code returned by Postgres (if any)', + name: 'postgresql.log.error.code', + type: 'long', + }, + 'postgresql.log.timezone': { + category: 'postgresql', + name: 'postgresql.log.timezone', + type: 'alias', + }, + 'postgresql.log.thread_id': { + category: 'postgresql', + name: 'postgresql.log.thread_id', + type: 'alias', + }, + 'postgresql.log.user': { + category: 'postgresql', + name: 'postgresql.log.user', + type: 'alias', + }, + 'postgresql.log.level': { + category: 'postgresql', + name: 'postgresql.log.level', + type: 'alias', + }, + 'postgresql.log.message': { + category: 'postgresql', + name: 'postgresql.log.message', + type: 'alias', + }, + 'redis.log.role': { + category: 'redis', + description: + 'The role of the Redis instance. Can be one of `master`, `slave`, `child` (for RDF/AOF writing child), or `sentinel`. ', + name: 'redis.log.role', + type: 'keyword', + }, + 'redis.log.pid': { + category: 'redis', + name: 'redis.log.pid', + type: 'alias', + }, + 'redis.log.level': { + category: 'redis', + name: 'redis.log.level', + type: 'alias', + }, + 'redis.log.message': { + category: 'redis', + name: 'redis.log.message', + type: 'alias', + }, + 'redis.slowlog.cmd': { + category: 'redis', + description: 'The command executed. ', + name: 'redis.slowlog.cmd', + type: 'keyword', + }, + 'redis.slowlog.duration.us': { + category: 'redis', + description: 'How long it took to execute the command in microseconds. ', + name: 'redis.slowlog.duration.us', + type: 'long', + }, + 'redis.slowlog.id': { + category: 'redis', + description: 'The ID of the query. ', + name: 'redis.slowlog.id', + type: 'long', + }, + 'redis.slowlog.key': { + category: 'redis', + description: 'The key on which the command was executed. ', + name: 'redis.slowlog.key', + type: 'keyword', + }, + 'redis.slowlog.args': { + category: 'redis', + description: 'The arguments with which the command was called. ', + name: 'redis.slowlog.args', + type: 'keyword', + }, + 'santa.action': { + category: 'santa', + description: 'Action', + example: 'EXEC', + name: 'santa.action', + type: 'keyword', + }, + 'santa.decision': { + category: 'santa', + description: 'Decision that santad took.', + example: 'ALLOW', + name: 'santa.decision', + type: 'keyword', + }, + 'santa.reason': { + category: 'santa', + description: 'Reason for the decsision.', + example: 'CERT', + name: 'santa.reason', + type: 'keyword', + }, + 'santa.mode': { + category: 'santa', + description: 'Operating mode of Santa.', + example: 'M', + name: 'santa.mode', + type: 'keyword', + }, + 'santa.disk.volume': { + category: 'santa', + description: 'The volume name.', + name: 'santa.disk.volume', + }, + 'santa.disk.bus': { + category: 'santa', + description: 'The disk bus protocol.', + name: 'santa.disk.bus', + }, + 'santa.disk.serial': { + category: 'santa', + description: 'The disk serial number.', + name: 'santa.disk.serial', + }, + 'santa.disk.bsdname': { + category: 'santa', + description: 'The disk BSD name.', + example: 'disk1s3', + name: 'santa.disk.bsdname', + }, + 'santa.disk.model': { + category: 'santa', + description: 'The disk model.', + example: 'APPLE SSD SM0512L', + name: 'santa.disk.model', + }, + 'santa.disk.fs': { + category: 'santa', + description: 'The disk volume kind (filesystem type).', + example: 'apfs', + name: 'santa.disk.fs', + }, + 'santa.disk.mount': { + category: 'santa', + description: 'The disk volume path.', + name: 'santa.disk.mount', + }, + 'santa.certificate.common_name': { + category: 'santa', + description: 'Common name from code signing certificate.', + name: 'santa.certificate.common_name', + type: 'keyword', + }, + 'santa.certificate.sha256': { + category: 'santa', + description: 'SHA256 hash of code signing certificate.', + name: 'santa.certificate.sha256', + type: 'keyword', + }, + 'system.auth.timestamp': { + category: 'system', + name: 'system.auth.timestamp', + type: 'alias', + }, + 'system.auth.hostname': { + category: 'system', + name: 'system.auth.hostname', + type: 'alias', + }, + 'system.auth.program': { + category: 'system', + name: 'system.auth.program', + type: 'alias', + }, + 'system.auth.pid': { + category: 'system', + name: 'system.auth.pid', + type: 'alias', + }, + 'system.auth.message': { + category: 'system', + name: 'system.auth.message', + type: 'alias', + }, + 'system.auth.user': { + category: 'system', + name: 'system.auth.user', + type: 'alias', + }, + 'system.auth.ssh.method': { + category: 'system', + description: 'The SSH authentication method. Can be one of "password" or "publickey". ', + name: 'system.auth.ssh.method', + }, + 'system.auth.ssh.signature': { + category: 'system', + description: 'The signature of the client public key. ', + name: 'system.auth.ssh.signature', + }, + 'system.auth.ssh.dropped_ip': { + category: 'system', + description: 'The client IP from SSH connections that are open and immediately dropped. ', + name: 'system.auth.ssh.dropped_ip', + type: 'ip', + }, + 'system.auth.ssh.event': { + category: 'system', + description: 'The SSH event as found in the logs (Accepted, Invalid, Failed, etc.) ', + example: 'Accepted', + name: 'system.auth.ssh.event', + }, + 'system.auth.ssh.ip': { + category: 'system', + name: 'system.auth.ssh.ip', + type: 'alias', + }, + 'system.auth.ssh.port': { + category: 'system', + name: 'system.auth.ssh.port', + type: 'alias', + }, + 'system.auth.ssh.geoip.continent_name': { + category: 'system', + name: 'system.auth.ssh.geoip.continent_name', + type: 'alias', + }, + 'system.auth.ssh.geoip.country_iso_code': { + category: 'system', + name: 'system.auth.ssh.geoip.country_iso_code', + type: 'alias', + }, + 'system.auth.ssh.geoip.location': { + category: 'system', + name: 'system.auth.ssh.geoip.location', + type: 'alias', + }, + 'system.auth.ssh.geoip.region_name': { + category: 'system', + name: 'system.auth.ssh.geoip.region_name', + type: 'alias', + }, + 'system.auth.ssh.geoip.city_name': { + category: 'system', + name: 'system.auth.ssh.geoip.city_name', + type: 'alias', + }, + 'system.auth.ssh.geoip.region_iso_code': { + category: 'system', + name: 'system.auth.ssh.geoip.region_iso_code', + type: 'alias', + }, + 'system.auth.sudo.error': { + category: 'system', + description: 'The error message in case the sudo command failed. ', + example: 'user NOT in sudoers', + name: 'system.auth.sudo.error', + }, + 'system.auth.sudo.tty': { + category: 'system', + description: 'The TTY where the sudo command is executed. ', + name: 'system.auth.sudo.tty', + }, + 'system.auth.sudo.pwd': { + category: 'system', + description: 'The current directory where the sudo command is executed. ', + name: 'system.auth.sudo.pwd', + }, + 'system.auth.sudo.user': { + category: 'system', + description: 'The target user to which the sudo command is switching. ', + example: 'root', + name: 'system.auth.sudo.user', + }, + 'system.auth.sudo.command': { + category: 'system', + description: 'The command executed via sudo. ', + name: 'system.auth.sudo.command', + }, + 'system.auth.useradd.home': { + category: 'system', + description: 'The home folder for the new user.', + name: 'system.auth.useradd.home', + }, + 'system.auth.useradd.shell': { + category: 'system', + description: 'The default shell for the new user.', + name: 'system.auth.useradd.shell', + }, + 'system.auth.useradd.name': { + category: 'system', + name: 'system.auth.useradd.name', + type: 'alias', + }, + 'system.auth.useradd.uid': { + category: 'system', + name: 'system.auth.useradd.uid', + type: 'alias', + }, + 'system.auth.useradd.gid': { + category: 'system', + name: 'system.auth.useradd.gid', + type: 'alias', + }, + 'system.auth.groupadd.name': { + category: 'system', + name: 'system.auth.groupadd.name', + type: 'alias', + }, + 'system.auth.groupadd.gid': { + category: 'system', + name: 'system.auth.groupadd.gid', + type: 'alias', + }, + 'system.syslog.timestamp': { + category: 'system', + name: 'system.syslog.timestamp', + type: 'alias', + }, + 'system.syslog.hostname': { + category: 'system', + name: 'system.syslog.hostname', + type: 'alias', + }, + 'system.syslog.program': { + category: 'system', + name: 'system.syslog.program', + type: 'alias', + }, + 'system.syslog.pid': { + category: 'system', + name: 'system.syslog.pid', + type: 'alias', + }, + 'system.syslog.message': { + category: 'system', + name: 'system.syslog.message', + type: 'alias', + }, + 'traefik.access.user_identifier': { + category: 'traefik', + description: 'Is the RFC 1413 identity of the client ', + name: 'traefik.access.user_identifier', + type: 'keyword', + }, + 'traefik.access.request_count': { + category: 'traefik', + description: 'The number of requests ', + name: 'traefik.access.request_count', + type: 'long', + }, + 'traefik.access.frontend_name': { + category: 'traefik', + description: 'The name of the frontend used ', + name: 'traefik.access.frontend_name', + type: 'keyword', + }, + 'traefik.access.backend_url': { + category: 'traefik', + description: 'The url of the backend where request is forwarded', + name: 'traefik.access.backend_url', + type: 'keyword', + }, + 'traefik.access.body_sent.bytes': { + category: 'traefik', + name: 'traefik.access.body_sent.bytes', + type: 'alias', + }, + 'traefik.access.remote_ip': { + category: 'traefik', + name: 'traefik.access.remote_ip', + type: 'alias', + }, + 'traefik.access.user_name': { + category: 'traefik', + name: 'traefik.access.user_name', + type: 'alias', + }, + 'traefik.access.method': { + category: 'traefik', + name: 'traefik.access.method', + type: 'alias', + }, + 'traefik.access.url': { + category: 'traefik', + name: 'traefik.access.url', + type: 'alias', + }, + 'traefik.access.http_version': { + category: 'traefik', + name: 'traefik.access.http_version', + type: 'alias', + }, + 'traefik.access.response_code': { + category: 'traefik', + name: 'traefik.access.response_code', + type: 'alias', + }, + 'traefik.access.referrer': { + category: 'traefik', + name: 'traefik.access.referrer', + type: 'alias', + }, + 'traefik.access.agent': { + category: 'traefik', + name: 'traefik.access.agent', + type: 'alias', + }, + 'traefik.access.user_agent.device': { + category: 'traefik', + name: 'traefik.access.user_agent.device', + type: 'alias', + }, + 'traefik.access.user_agent.name': { + category: 'traefik', + name: 'traefik.access.user_agent.name', + type: 'alias', + }, + 'traefik.access.user_agent.os': { + category: 'traefik', + name: 'traefik.access.user_agent.os', + type: 'alias', + }, + 'traefik.access.user_agent.os_name': { + category: 'traefik', + name: 'traefik.access.user_agent.os_name', + type: 'alias', + }, + 'traefik.access.user_agent.original': { + category: 'traefik', + name: 'traefik.access.user_agent.original', + type: 'alias', + }, + 'traefik.access.geoip.continent_name': { + category: 'traefik', + name: 'traefik.access.geoip.continent_name', + type: 'alias', + }, + 'traefik.access.geoip.country_iso_code': { + category: 'traefik', + name: 'traefik.access.geoip.country_iso_code', + type: 'alias', + }, + 'traefik.access.geoip.location': { + category: 'traefik', + name: 'traefik.access.geoip.location', + type: 'alias', + }, + 'traefik.access.geoip.region_name': { + category: 'traefik', + name: 'traefik.access.geoip.region_name', + type: 'alias', + }, + 'traefik.access.geoip.city_name': { + category: 'traefik', + name: 'traefik.access.geoip.city_name', + type: 'alias', + }, + 'traefik.access.geoip.region_iso_code': { + category: 'traefik', + name: 'traefik.access.geoip.region_iso_code', + type: 'alias', + }, + 'activemq.caller': { + category: 'activemq', + description: 'Name of the caller issuing the logging request (class or resource). ', + name: 'activemq.caller', + type: 'keyword', + }, + 'activemq.thread': { + category: 'activemq', + description: 'Thread that generated the logging event. ', + name: 'activemq.thread', + type: 'keyword', + }, + 'activemq.user': { + category: 'activemq', + description: 'User that generated the logging event. ', + name: 'activemq.user', + type: 'keyword', + }, + 'activemq.audit': { + category: 'activemq', + description: 'Fields from ActiveMQ audit logs. ', + name: 'activemq.audit', + type: 'group', + }, + 'activemq.log.stack_trace': { + category: 'activemq', + name: 'activemq.log.stack_trace', + type: 'keyword', + }, + 'aws.cloudtrail.event_version': { + category: 'aws', + description: 'The CloudTrail version of the log event format. ', + name: 'aws.cloudtrail.event_version', + type: 'keyword', + }, + 'aws.cloudtrail.user_identity.type': { + category: 'aws', + description: 'The type of the identity ', + name: 'aws.cloudtrail.user_identity.type', + type: 'keyword', + }, + 'aws.cloudtrail.user_identity.arn': { + category: 'aws', + description: 'The Amazon Resource Name (ARN) of the principal that made the call.', + name: 'aws.cloudtrail.user_identity.arn', + type: 'keyword', + }, + 'aws.cloudtrail.user_identity.access_key_id': { + category: 'aws', + description: 'The access key ID that was used to sign the request.', + name: 'aws.cloudtrail.user_identity.access_key_id', + type: 'keyword', + }, + 'aws.cloudtrail.user_identity.session_context.mfa_authenticated': { + category: 'aws', + description: + 'The value is true if the root user or IAM user whose credentials were used for the request also was authenticated with an MFA device; otherwise, false.', + name: 'aws.cloudtrail.user_identity.session_context.mfa_authenticated', + type: 'keyword', + }, + 'aws.cloudtrail.user_identity.session_context.creation_date': { + category: 'aws', + description: 'The date and time when the temporary security credentials were issued.', + name: 'aws.cloudtrail.user_identity.session_context.creation_date', + type: 'date', + }, + 'aws.cloudtrail.user_identity.session_context.session_issuer.type': { + category: 'aws', + description: + 'The source of the temporary security credentials, such as Root, IAMUser, or Role.', + name: 'aws.cloudtrail.user_identity.session_context.session_issuer.type', + type: 'keyword', + }, + 'aws.cloudtrail.user_identity.session_context.session_issuer.principal_id': { + category: 'aws', + description: 'The internal ID of the entity that was used to get credentials.', + name: 'aws.cloudtrail.user_identity.session_context.session_issuer.principal_id', + type: 'keyword', + }, + 'aws.cloudtrail.user_identity.session_context.session_issuer.arn': { + category: 'aws', + description: + 'The ARN of the source (account, IAM user, or role) that was used to get temporary security credentials.', + name: 'aws.cloudtrail.user_identity.session_context.session_issuer.arn', + type: 'keyword', + }, + 'aws.cloudtrail.user_identity.session_context.session_issuer.account_id': { + category: 'aws', + description: 'The account that owns the entity that was used to get credentials.', + name: 'aws.cloudtrail.user_identity.session_context.session_issuer.account_id', + type: 'keyword', + }, + 'aws.cloudtrail.user_identity.invoked_by': { + category: 'aws', + description: + 'The name of the AWS service that made the request, such as Amazon EC2 Auto Scaling or AWS Elastic Beanstalk.', + name: 'aws.cloudtrail.user_identity.invoked_by', + type: 'keyword', + }, + 'aws.cloudtrail.error_code': { + category: 'aws', + description: 'The AWS service error if the request returns an error.', + name: 'aws.cloudtrail.error_code', + type: 'keyword', + }, + 'aws.cloudtrail.error_message': { + category: 'aws', + description: 'If the request returns an error, the description of the error.', + name: 'aws.cloudtrail.error_message', + type: 'keyword', + }, + 'aws.cloudtrail.request_parameters': { + category: 'aws', + description: 'The parameters, if any, that were sent with the request.', + name: 'aws.cloudtrail.request_parameters', + type: 'keyword', + }, + 'aws.cloudtrail.response_elements': { + category: 'aws', + description: + 'The response element for actions that make changes (create, update, or delete actions).', + name: 'aws.cloudtrail.response_elements', + type: 'keyword', + }, + 'aws.cloudtrail.additional_eventdata': { + category: 'aws', + description: 'Additional data about the event that was not part of the request or response.', + name: 'aws.cloudtrail.additional_eventdata', + type: 'keyword', + }, + 'aws.cloudtrail.request_id': { + category: 'aws', + description: + 'The value that identifies the request. The service being called generates this value.', + name: 'aws.cloudtrail.request_id', + type: 'keyword', + }, + 'aws.cloudtrail.event_type': { + category: 'aws', + description: 'Identifies the type of event that generated the event record.', + name: 'aws.cloudtrail.event_type', + type: 'keyword', + }, + 'aws.cloudtrail.api_version': { + category: 'aws', + description: 'Identifies the API version associated with the AwsApiCall eventType value.', + name: 'aws.cloudtrail.api_version', + type: 'keyword', + }, + 'aws.cloudtrail.management_event': { + category: 'aws', + description: 'A Boolean value that identifies whether the event is a management event.', + name: 'aws.cloudtrail.management_event', + type: 'keyword', + }, + 'aws.cloudtrail.read_only': { + category: 'aws', + description: 'Identifies whether this operation is a read-only operation.', + name: 'aws.cloudtrail.read_only', + type: 'keyword', + }, + 'aws.cloudtrail.resources.arn': { + category: 'aws', + description: 'Resource ARNs', + name: 'aws.cloudtrail.resources.arn', + type: 'keyword', + }, + 'aws.cloudtrail.resources.account_id': { + category: 'aws', + description: 'Account ID of the resource owner', + name: 'aws.cloudtrail.resources.account_id', + type: 'keyword', + }, + 'aws.cloudtrail.resources.type': { + category: 'aws', + description: 'Resource type identifier in the format: AWS::aws-service-name::data-type-name', + name: 'aws.cloudtrail.resources.type', + type: 'keyword', + }, + 'aws.cloudtrail.recipient_account_id': { + category: 'aws', + description: 'Represents the account ID that received this event.', + name: 'aws.cloudtrail.recipient_account_id', + type: 'keyword', + }, + 'aws.cloudtrail.service_event_details': { + category: 'aws', + description: 'Identifies the service event, including what triggered the event and the result.', + name: 'aws.cloudtrail.service_event_details', + type: 'keyword', + }, + 'aws.cloudtrail.shared_event_id': { + category: 'aws', + description: + 'GUID generated by CloudTrail to uniquely identify CloudTrail events from the same AWS action that is sent to different AWS accounts.', + name: 'aws.cloudtrail.shared_event_id', + type: 'keyword', + }, + 'aws.cloudtrail.vpc_endpoint_id': { + category: 'aws', + description: + 'Identifies the VPC endpoint in which requests were made from a VPC to another AWS service, such as Amazon S3.', + name: 'aws.cloudtrail.vpc_endpoint_id', + type: 'keyword', + }, + 'aws.cloudtrail.console_login.additional_eventdata.mobile_version': { + category: 'aws', + description: 'Identifies whether ConsoleLogin was from mobile version', + name: 'aws.cloudtrail.console_login.additional_eventdata.mobile_version', + type: 'boolean', + }, + 'aws.cloudtrail.console_login.additional_eventdata.login_to': { + category: 'aws', + description: 'URL for ConsoleLogin', + name: 'aws.cloudtrail.console_login.additional_eventdata.login_to', + type: 'keyword', + }, + 'aws.cloudtrail.console_login.additional_eventdata.mfa_used': { + category: 'aws', + description: 'Identifies whether multi factor authentication was used during ConsoleLogin', + name: 'aws.cloudtrail.console_login.additional_eventdata.mfa_used', + type: 'boolean', + }, + 'aws.cloudtrail.flattened.additional_eventdata': { + category: 'aws', + description: 'Additional data about the event that was not part of the request or response. ', + name: 'aws.cloudtrail.flattened.additional_eventdata', + type: 'flattened', + }, + 'aws.cloudtrail.flattened.request_parameters': { + category: 'aws', + description: 'The parameters, if any, that were sent with the request.', + name: 'aws.cloudtrail.flattened.request_parameters', + type: 'flattened', + }, + 'aws.cloudtrail.flattened.response_elements': { + category: 'aws', + description: + 'The response element for actions that make changes (create, update, or delete actions).', + name: 'aws.cloudtrail.flattened.response_elements', + type: 'flattened', + }, + 'aws.cloudtrail.flattened.service_event_details': { + category: 'aws', + description: 'Identifies the service event, including what triggered the event and the result.', + name: 'aws.cloudtrail.flattened.service_event_details', + type: 'flattened', + }, + 'aws.cloudwatch.message': { + category: 'aws', + description: 'CloudWatch log message. ', + name: 'aws.cloudwatch.message', + type: 'text', + }, + 'aws.ec2.ip_address': { + category: 'aws', + description: 'The internet address of the requester. ', + name: 'aws.ec2.ip_address', + type: 'keyword', + }, + 'aws.elb.name': { + category: 'aws', + description: 'The name of the load balancer. ', + name: 'aws.elb.name', + type: 'keyword', + }, + 'aws.elb.type': { + category: 'aws', + description: 'The type of the load balancer for v2 Load Balancers. ', + name: 'aws.elb.type', + type: 'keyword', + }, + 'aws.elb.target_group.arn': { + category: 'aws', + description: 'The ARN of the target group handling the request. ', + name: 'aws.elb.target_group.arn', + type: 'keyword', + }, + 'aws.elb.listener': { + category: 'aws', + description: 'The ELB listener that received the connection. ', + name: 'aws.elb.listener', + type: 'keyword', + }, + 'aws.elb.protocol': { + category: 'aws', + description: 'The protocol of the load balancer (http or tcp). ', + name: 'aws.elb.protocol', + type: 'keyword', + }, + 'aws.elb.request_processing_time.sec': { + category: 'aws', + description: + 'The total time in seconds since the connection or request is received until it is sent to a registered backend. ', + name: 'aws.elb.request_processing_time.sec', + type: 'float', + }, + 'aws.elb.backend_processing_time.sec': { + category: 'aws', + description: + 'The total time in seconds since the connection is sent to the backend till the backend starts responding. ', + name: 'aws.elb.backend_processing_time.sec', + type: 'float', + }, + 'aws.elb.response_processing_time.sec': { + category: 'aws', + description: + 'The total time in seconds since the response is received from the backend till it is sent to the client. ', + name: 'aws.elb.response_processing_time.sec', + type: 'float', + }, + 'aws.elb.connection_time.ms': { + category: 'aws', + description: + 'The total time of the connection in milliseconds, since it is opened till it is closed. ', + name: 'aws.elb.connection_time.ms', + type: 'long', + }, + 'aws.elb.tls_handshake_time.ms': { + category: 'aws', + description: + 'The total time for the TLS handshake to complete in milliseconds once the connection has been established. ', + name: 'aws.elb.tls_handshake_time.ms', + type: 'long', + }, + 'aws.elb.backend.ip': { + category: 'aws', + description: 'The IP address of the backend processing this connection. ', + name: 'aws.elb.backend.ip', + type: 'keyword', + }, + 'aws.elb.backend.port': { + category: 'aws', + description: 'The port in the backend processing this connection. ', + name: 'aws.elb.backend.port', + type: 'keyword', + }, + 'aws.elb.backend.http.response.status_code': { + category: 'aws', + description: + 'The status code from the backend (status code sent to the client from ELB is stored in `http.response.status_code` ', + name: 'aws.elb.backend.http.response.status_code', + type: 'keyword', + }, + 'aws.elb.ssl_cipher': { + category: 'aws', + description: 'The SSL cipher used in TLS/SSL connections. ', + name: 'aws.elb.ssl_cipher', + type: 'keyword', + }, + 'aws.elb.ssl_protocol': { + category: 'aws', + description: 'The SSL protocol used in TLS/SSL connections. ', + name: 'aws.elb.ssl_protocol', + type: 'keyword', + }, + 'aws.elb.chosen_cert.arn': { + category: 'aws', + description: + 'The ARN of the chosen certificate presented to the client in TLS/SSL connections. ', + name: 'aws.elb.chosen_cert.arn', + type: 'keyword', + }, + 'aws.elb.chosen_cert.serial': { + category: 'aws', + description: + 'The serial number of the chosen certificate presented to the client in TLS/SSL connections. ', + name: 'aws.elb.chosen_cert.serial', + type: 'keyword', + }, + 'aws.elb.incoming_tls_alert': { + category: 'aws', + description: + 'The integer value of TLS alerts received by the load balancer from the client, if present. ', + name: 'aws.elb.incoming_tls_alert', + type: 'keyword', + }, + 'aws.elb.tls_named_group': { + category: 'aws', + description: 'The TLS named group. ', + name: 'aws.elb.tls_named_group', + type: 'keyword', + }, + 'aws.elb.trace_id': { + category: 'aws', + description: 'The contents of the `X-Amzn-Trace-Id` header. ', + name: 'aws.elb.trace_id', + type: 'keyword', + }, + 'aws.elb.matched_rule_priority': { + category: 'aws', + description: 'The priority value of the rule that matched the request, if a rule matched. ', + name: 'aws.elb.matched_rule_priority', + type: 'keyword', + }, + 'aws.elb.action_executed': { + category: 'aws', + description: + 'The action executed when processing the request (forward, fixed-response, authenticate...). It can contain several values. ', + name: 'aws.elb.action_executed', + type: 'keyword', + }, + 'aws.elb.redirect_url': { + category: 'aws', + description: 'The URL used if a redirection action was executed. ', + name: 'aws.elb.redirect_url', + type: 'keyword', + }, + 'aws.elb.error.reason': { + category: 'aws', + description: 'The error reason if the executed action failed. ', + name: 'aws.elb.error.reason', + type: 'keyword', + }, + 'aws.s3access.bucket_owner': { + category: 'aws', + description: 'The canonical user ID of the owner of the source bucket. ', + name: 'aws.s3access.bucket_owner', + type: 'keyword', + }, + 'aws.s3access.bucket': { + category: 'aws', + description: 'The name of the bucket that the request was processed against. ', + name: 'aws.s3access.bucket', + type: 'keyword', + }, + 'aws.s3access.remote_ip': { + category: 'aws', + description: 'The apparent internet address of the requester. ', + name: 'aws.s3access.remote_ip', + type: 'ip', + }, + 'aws.s3access.requester': { + category: 'aws', + description: 'The canonical user ID of the requester, or a - for unauthenticated requests. ', + name: 'aws.s3access.requester', + type: 'keyword', + }, + 'aws.s3access.request_id': { + category: 'aws', + description: 'A string generated by Amazon S3 to uniquely identify each request. ', + name: 'aws.s3access.request_id', + type: 'keyword', + }, + 'aws.s3access.operation': { + category: 'aws', + description: + 'The operation listed here is declared as SOAP.operation, REST.HTTP_method.resource_type, WEBSITE.HTTP_method.resource_type, or BATCH.DELETE.OBJECT. ', + name: 'aws.s3access.operation', + type: 'keyword', + }, + 'aws.s3access.key': { + category: 'aws', + description: + 'The "key" part of the request, URL encoded, or "-" if the operation does not take a key parameter. ', + name: 'aws.s3access.key', + type: 'keyword', + }, + 'aws.s3access.request_uri': { + category: 'aws', + description: 'The Request-URI part of the HTTP request message. ', + name: 'aws.s3access.request_uri', + type: 'keyword', + }, + 'aws.s3access.http_status': { + category: 'aws', + description: 'The numeric HTTP status code of the response. ', + name: 'aws.s3access.http_status', + type: 'long', + }, + 'aws.s3access.error_code': { + category: 'aws', + description: 'The Amazon S3 Error Code, or "-" if no error occurred. ', + name: 'aws.s3access.error_code', + type: 'keyword', + }, + 'aws.s3access.bytes_sent': { + category: 'aws', + description: + 'The number of response bytes sent, excluding HTTP protocol overhead, or "-" if zero. ', + name: 'aws.s3access.bytes_sent', + type: 'long', + }, + 'aws.s3access.object_size': { + category: 'aws', + description: 'The total size of the object in question. ', + name: 'aws.s3access.object_size', + type: 'long', + }, + 'aws.s3access.total_time': { + category: 'aws', + description: + "The number of milliseconds the request was in flight from the server's perspective. ", + name: 'aws.s3access.total_time', + type: 'long', + }, + 'aws.s3access.turn_around_time': { + category: 'aws', + description: 'The number of milliseconds that Amazon S3 spent processing your request. ', + name: 'aws.s3access.turn_around_time', + type: 'long', + }, + 'aws.s3access.referrer': { + category: 'aws', + description: 'The value of the HTTP Referrer header, if present. ', + name: 'aws.s3access.referrer', + type: 'keyword', + }, + 'aws.s3access.user_agent': { + category: 'aws', + description: 'The value of the HTTP User-Agent header. ', + name: 'aws.s3access.user_agent', + type: 'keyword', + }, + 'aws.s3access.version_id': { + category: 'aws', + description: + 'The version ID in the request, or "-" if the operation does not take a versionId parameter. ', + name: 'aws.s3access.version_id', + type: 'keyword', + }, + 'aws.s3access.host_id': { + category: 'aws', + description: 'The x-amz-id-2 or Amazon S3 extended request ID. ', + name: 'aws.s3access.host_id', + type: 'keyword', + }, + 'aws.s3access.signature_version': { + category: 'aws', + description: + 'The signature version, SigV2 or SigV4, that was used to authenticate the request or a - for unauthenticated requests. ', + name: 'aws.s3access.signature_version', + type: 'keyword', + }, + 'aws.s3access.cipher_suite': { + category: 'aws', + description: + 'The Secure Sockets Layer (SSL) cipher that was negotiated for HTTPS request or a - for HTTP. ', + name: 'aws.s3access.cipher_suite', + type: 'keyword', + }, + 'aws.s3access.authentication_type': { + category: 'aws', + description: + 'The type of request authentication used, AuthHeader for authentication headers, QueryString for query string (pre-signed URL) or a - for unauthenticated requests. ', + name: 'aws.s3access.authentication_type', + type: 'keyword', + }, + 'aws.s3access.host_header': { + category: 'aws', + description: 'The endpoint used to connect to Amazon S3. ', + name: 'aws.s3access.host_header', + type: 'keyword', + }, + 'aws.s3access.tls_version': { + category: 'aws', + description: 'The Transport Layer Security (TLS) version negotiated by the client. ', + name: 'aws.s3access.tls_version', + type: 'keyword', + }, + 'aws.vpcflow.version': { + category: 'aws', + description: + 'The VPC Flow Logs version. If you use the default format, the version is 2. If you specify a custom format, the version is 3. ', + name: 'aws.vpcflow.version', + type: 'keyword', + }, + 'aws.vpcflow.account_id': { + category: 'aws', + description: 'The AWS account ID for the flow log. ', + name: 'aws.vpcflow.account_id', + type: 'keyword', + }, + 'aws.vpcflow.interface_id': { + category: 'aws', + description: 'The ID of the network interface for which the traffic is recorded. ', + name: 'aws.vpcflow.interface_id', + type: 'keyword', + }, + 'aws.vpcflow.action': { + category: 'aws', + description: 'The action that is associated with the traffic, ACCEPT or REJECT. ', + name: 'aws.vpcflow.action', + type: 'keyword', + }, + 'aws.vpcflow.log_status': { + category: 'aws', + description: 'The logging status of the flow log, OK, NODATA or SKIPDATA. ', + name: 'aws.vpcflow.log_status', + type: 'keyword', + }, + 'aws.vpcflow.instance_id': { + category: 'aws', + description: + "The ID of the instance that's associated with network interface for which the traffic is recorded, if the instance is owned by you. ", + name: 'aws.vpcflow.instance_id', + type: 'keyword', + }, + 'aws.vpcflow.pkt_srcaddr': { + category: 'aws', + description: 'The packet-level (original) source IP address of the traffic. ', + name: 'aws.vpcflow.pkt_srcaddr', + type: 'ip', + }, + 'aws.vpcflow.pkt_dstaddr': { + category: 'aws', + description: 'The packet-level (original) destination IP address for the traffic. ', + name: 'aws.vpcflow.pkt_dstaddr', + type: 'ip', + }, + 'aws.vpcflow.vpc_id': { + category: 'aws', + description: + 'The ID of the VPC that contains the network interface for which the traffic is recorded. ', + name: 'aws.vpcflow.vpc_id', + type: 'keyword', + }, + 'aws.vpcflow.subnet_id': { + category: 'aws', + description: + 'The ID of the subnet that contains the network interface for which the traffic is recorded. ', + name: 'aws.vpcflow.subnet_id', + type: 'keyword', + }, + 'aws.vpcflow.tcp_flags': { + category: 'aws', + description: 'The bitmask value for the following TCP flags: 2=SYN,18=SYN-ACK,1=FIN,4=RST ', + name: 'aws.vpcflow.tcp_flags', + type: 'keyword', + }, + 'aws.vpcflow.type': { + category: 'aws', + description: 'The type of traffic: IPv4, IPv6, or EFA. ', + name: 'aws.vpcflow.type', + type: 'keyword', + }, + 'azure.subscription_id': { + category: 'azure', + description: 'Azure subscription ID ', + name: 'azure.subscription_id', + type: 'keyword', + }, + 'azure.correlation_id': { + category: 'azure', + description: 'Correlation ID ', + name: 'azure.correlation_id', + type: 'keyword', + }, + 'azure.tenant_id': { + category: 'azure', + description: 'tenant ID ', + name: 'azure.tenant_id', + type: 'keyword', + }, + 'azure.resource.id': { + category: 'azure', + description: 'Resource ID ', + name: 'azure.resource.id', + type: 'keyword', + }, + 'azure.resource.group': { + category: 'azure', + description: 'Resource group ', + name: 'azure.resource.group', + type: 'keyword', + }, + 'azure.resource.provider': { + category: 'azure', + description: 'Resource type/namespace ', + name: 'azure.resource.provider', + type: 'keyword', + }, + 'azure.resource.namespace': { + category: 'azure', + description: 'Resource type/namespace ', + name: 'azure.resource.namespace', + type: 'keyword', + }, + 'azure.resource.name': { + category: 'azure', + description: 'Name ', + name: 'azure.resource.name', + type: 'keyword', + }, + 'azure.resource.authorization_rule': { + category: 'azure', + description: 'Authorization rule ', + name: 'azure.resource.authorization_rule', + type: 'keyword', + }, + 'azure.activitylogs.identity.claims_initiated_by_user.name': { + category: 'azure', + description: 'Name ', + name: 'azure.activitylogs.identity.claims_initiated_by_user.name', + type: 'keyword', + }, + 'azure.activitylogs.identity.claims_initiated_by_user.givenname': { + category: 'azure', + description: 'Givenname ', + name: 'azure.activitylogs.identity.claims_initiated_by_user.givenname', + type: 'keyword', + }, + 'azure.activitylogs.identity.claims_initiated_by_user.surname': { + category: 'azure', + description: 'Surname ', + name: 'azure.activitylogs.identity.claims_initiated_by_user.surname', + type: 'keyword', + }, + 'azure.activitylogs.identity.claims_initiated_by_user.fullname': { + category: 'azure', + description: 'Fullname ', + name: 'azure.activitylogs.identity.claims_initiated_by_user.fullname', + type: 'keyword', + }, + 'azure.activitylogs.identity.claims_initiated_by_user.schema': { + category: 'azure', + description: 'Schema ', + name: 'azure.activitylogs.identity.claims_initiated_by_user.schema', + type: 'keyword', + }, + 'azure.activitylogs.identity.claims.*': { + category: 'azure', + description: 'Claims ', + name: 'azure.activitylogs.identity.claims.*', + type: 'object', + }, + 'azure.activitylogs.identity.authorization.scope': { + category: 'azure', + description: 'Scope ', + name: 'azure.activitylogs.identity.authorization.scope', + type: 'keyword', + }, + 'azure.activitylogs.identity.authorization.action': { + category: 'azure', + description: 'Action ', + name: 'azure.activitylogs.identity.authorization.action', + type: 'keyword', + }, + 'azure.activitylogs.identity.authorization.evidence.role_assignment_scope': { + category: 'azure', + description: 'Role assignment scope ', + name: 'azure.activitylogs.identity.authorization.evidence.role_assignment_scope', + type: 'keyword', + }, + 'azure.activitylogs.identity.authorization.evidence.role_definition_id': { + category: 'azure', + description: 'Role definition ID ', + name: 'azure.activitylogs.identity.authorization.evidence.role_definition_id', + type: 'keyword', + }, + 'azure.activitylogs.identity.authorization.evidence.role': { + category: 'azure', + description: 'Role ', + name: 'azure.activitylogs.identity.authorization.evidence.role', + type: 'keyword', + }, + 'azure.activitylogs.identity.authorization.evidence.role_assignment_id': { + category: 'azure', + description: 'Role assignment ID ', + name: 'azure.activitylogs.identity.authorization.evidence.role_assignment_id', + type: 'keyword', + }, + 'azure.activitylogs.identity.authorization.evidence.principal_id': { + category: 'azure', + description: 'Principal ID ', + name: 'azure.activitylogs.identity.authorization.evidence.principal_id', + type: 'keyword', + }, + 'azure.activitylogs.identity.authorization.evidence.principal_type': { + category: 'azure', + description: 'Principal type ', + name: 'azure.activitylogs.identity.authorization.evidence.principal_type', + type: 'keyword', + }, + 'azure.activitylogs.operation_name': { + category: 'azure', + description: 'Operation name ', + name: 'azure.activitylogs.operation_name', + type: 'keyword', + }, + 'azure.activitylogs.result_type': { + category: 'azure', + description: 'Result type ', + name: 'azure.activitylogs.result_type', + type: 'keyword', + }, + 'azure.activitylogs.result_signature': { + category: 'azure', + description: 'Result signature ', + name: 'azure.activitylogs.result_signature', + type: 'keyword', + }, + 'azure.activitylogs.category': { + category: 'azure', + description: 'Category ', + name: 'azure.activitylogs.category', + type: 'keyword', + }, + 'azure.activitylogs.event_category': { + category: 'azure', + description: 'Event Category ', + name: 'azure.activitylogs.event_category', + type: 'keyword', + }, + 'azure.activitylogs.properties.service_request_id': { + category: 'azure', + description: 'Service Request Id ', + name: 'azure.activitylogs.properties.service_request_id', + type: 'keyword', + }, + 'azure.activitylogs.properties.status_code': { + category: 'azure', + description: 'Status code ', + name: 'azure.activitylogs.properties.status_code', + type: 'keyword', + }, + 'azure.auditlogs.category': { + category: 'azure', + description: 'The category of the operation. Currently, Audit is the only supported value. ', + name: 'azure.auditlogs.category', + type: 'keyword', + }, + 'azure.auditlogs.operation_name': { + category: 'azure', + description: 'The operation name ', + name: 'azure.auditlogs.operation_name', + type: 'keyword', + }, + 'azure.auditlogs.operation_version': { + category: 'azure', + description: 'The operation version ', + name: 'azure.auditlogs.operation_version', + type: 'keyword', + }, + 'azure.auditlogs.identity': { + category: 'azure', + description: 'Identity ', + name: 'azure.auditlogs.identity', + type: 'keyword', + }, + 'azure.auditlogs.tenant_id': { + category: 'azure', + description: 'Tenant ID ', + name: 'azure.auditlogs.tenant_id', + type: 'keyword', + }, + 'azure.auditlogs.result_signature': { + category: 'azure', + description: 'Result signature ', + name: 'azure.auditlogs.result_signature', + type: 'keyword', + }, + 'azure.auditlogs.properties.result': { + category: 'azure', + description: 'Log result ', + name: 'azure.auditlogs.properties.result', + type: 'keyword', + }, + 'azure.auditlogs.properties.activity_display_name': { + category: 'azure', + description: 'Activity display name ', + name: 'azure.auditlogs.properties.activity_display_name', + type: 'keyword', + }, + 'azure.auditlogs.properties.result_reason': { + category: 'azure', + description: 'Reason for the log result ', + name: 'azure.auditlogs.properties.result_reason', + type: 'keyword', + }, + 'azure.auditlogs.properties.correlation_id': { + category: 'azure', + description: 'Correlation ID ', + name: 'azure.auditlogs.properties.correlation_id', + type: 'keyword', + }, + 'azure.auditlogs.properties.logged_by_service': { + category: 'azure', + description: 'Logged by service ', + name: 'azure.auditlogs.properties.logged_by_service', + type: 'keyword', + }, + 'azure.auditlogs.properties.operation_type': { + category: 'azure', + description: 'Operation type ', + name: 'azure.auditlogs.properties.operation_type', + type: 'keyword', + }, + 'azure.auditlogs.properties.id': { + category: 'azure', + description: 'ID ', + name: 'azure.auditlogs.properties.id', + type: 'keyword', + }, + 'azure.auditlogs.properties.activity_datetime': { + category: 'azure', + description: 'Activity timestamp ', + name: 'azure.auditlogs.properties.activity_datetime', + type: 'date', + }, + 'azure.auditlogs.properties.category': { + category: 'azure', + description: 'category ', + name: 'azure.auditlogs.properties.category', + type: 'keyword', + }, + 'azure.auditlogs.properties.target_resources.*.display_name': { + category: 'azure', + description: 'Display name ', + name: 'azure.auditlogs.properties.target_resources.*.display_name', + type: 'keyword', + }, + 'azure.auditlogs.properties.target_resources.*.id': { + category: 'azure', + description: 'ID ', + name: 'azure.auditlogs.properties.target_resources.*.id', + type: 'keyword', + }, + 'azure.auditlogs.properties.target_resources.*.type': { + category: 'azure', + description: 'Type ', + name: 'azure.auditlogs.properties.target_resources.*.type', + type: 'keyword', + }, + 'azure.auditlogs.properties.target_resources.*.ip_address': { + category: 'azure', + description: 'ip Address ', + name: 'azure.auditlogs.properties.target_resources.*.ip_address', + type: 'keyword', + }, + 'azure.auditlogs.properties.target_resources.*.user_principal_name': { + category: 'azure', + description: 'User principal name ', + name: 'azure.auditlogs.properties.target_resources.*.user_principal_name', + type: 'keyword', + }, + 'azure.auditlogs.properties.target_resources.*.modified_properties.*.new_value': { + category: 'azure', + description: 'New value ', + name: 'azure.auditlogs.properties.target_resources.*.modified_properties.*.new_value', + type: 'keyword', + }, + 'azure.auditlogs.properties.target_resources.*.modified_properties.*.display_name': { + category: 'azure', + description: 'Display value ', + name: 'azure.auditlogs.properties.target_resources.*.modified_properties.*.display_name', + type: 'keyword', + }, + 'azure.auditlogs.properties.target_resources.*.modified_properties.*.old_value': { + category: 'azure', + description: 'Old value ', + name: 'azure.auditlogs.properties.target_resources.*.modified_properties.*.old_value', + type: 'keyword', + }, + 'azure.auditlogs.properties.initiated_by.app.servicePrincipalName': { + category: 'azure', + description: 'Service principal name ', + name: 'azure.auditlogs.properties.initiated_by.app.servicePrincipalName', + type: 'keyword', + }, + 'azure.auditlogs.properties.initiated_by.app.displayName': { + category: 'azure', + description: 'Display name ', + name: 'azure.auditlogs.properties.initiated_by.app.displayName', + type: 'keyword', + }, + 'azure.auditlogs.properties.initiated_by.app.appId': { + category: 'azure', + description: 'App ID ', + name: 'azure.auditlogs.properties.initiated_by.app.appId', + type: 'keyword', + }, + 'azure.auditlogs.properties.initiated_by.app.servicePrincipalId': { + category: 'azure', + description: 'Service principal ID ', + name: 'azure.auditlogs.properties.initiated_by.app.servicePrincipalId', + type: 'keyword', + }, + 'azure.auditlogs.properties.initiated_by.user.userPrincipalName': { + category: 'azure', + description: 'User principal name ', + name: 'azure.auditlogs.properties.initiated_by.user.userPrincipalName', + type: 'keyword', + }, + 'azure.auditlogs.properties.initiated_by.user.displayName': { + category: 'azure', + description: 'Display name ', + name: 'azure.auditlogs.properties.initiated_by.user.displayName', + type: 'keyword', + }, + 'azure.auditlogs.properties.initiated_by.user.id': { + category: 'azure', + description: 'ID ', + name: 'azure.auditlogs.properties.initiated_by.user.id', + type: 'keyword', + }, + 'azure.auditlogs.properties.initiated_by.user.ipAddress': { + category: 'azure', + description: 'ip Address ', + name: 'azure.auditlogs.properties.initiated_by.user.ipAddress', + type: 'keyword', + }, + 'azure.signinlogs.operation_name': { + category: 'azure', + description: 'The operation name ', + name: 'azure.signinlogs.operation_name', + type: 'keyword', + }, + 'azure.signinlogs.operation_version': { + category: 'azure', + description: 'The operation version ', + name: 'azure.signinlogs.operation_version', + type: 'keyword', + }, + 'azure.signinlogs.tenant_id': { + category: 'azure', + description: 'Tenant ID ', + name: 'azure.signinlogs.tenant_id', + type: 'keyword', + }, + 'azure.signinlogs.result_signature': { + category: 'azure', + description: 'Result signature ', + name: 'azure.signinlogs.result_signature', + type: 'keyword', + }, + 'azure.signinlogs.result_description': { + category: 'azure', + description: 'Result description ', + name: 'azure.signinlogs.result_description', + type: 'keyword', + }, + 'azure.signinlogs.result_type': { + category: 'azure', + description: 'Result type ', + name: 'azure.signinlogs.result_type', + type: 'keyword', + }, + 'azure.signinlogs.identity': { + category: 'azure', + description: 'Identity ', + name: 'azure.signinlogs.identity', + type: 'keyword', + }, + 'azure.signinlogs.category': { + category: 'azure', + description: 'Category ', + name: 'azure.signinlogs.category', + type: 'keyword', + }, + 'azure.signinlogs.properties.id': { + category: 'azure', + description: 'ID ', + name: 'azure.signinlogs.properties.id', + type: 'keyword', + }, + 'azure.signinlogs.properties.created_at': { + category: 'azure', + description: 'Created date time ', + name: 'azure.signinlogs.properties.created_at', + type: 'date', + }, + 'azure.signinlogs.properties.user_display_name': { + category: 'azure', + description: 'User display name ', + name: 'azure.signinlogs.properties.user_display_name', + type: 'keyword', + }, + 'azure.signinlogs.properties.correlation_id': { + category: 'azure', + description: 'Correlation ID ', + name: 'azure.signinlogs.properties.correlation_id', + type: 'keyword', + }, + 'azure.signinlogs.properties.user_principal_name': { + category: 'azure', + description: 'User principal name ', + name: 'azure.signinlogs.properties.user_principal_name', + type: 'keyword', + }, + 'azure.signinlogs.properties.user_id': { + category: 'azure', + description: 'User ID ', + name: 'azure.signinlogs.properties.user_id', + type: 'keyword', + }, + 'azure.signinlogs.properties.app_id': { + category: 'azure', + description: 'App ID ', + name: 'azure.signinlogs.properties.app_id', + type: 'keyword', + }, + 'azure.signinlogs.properties.app_display_name': { + category: 'azure', + description: 'App display name ', + name: 'azure.signinlogs.properties.app_display_name', + type: 'keyword', + }, + 'azure.signinlogs.properties.ip_address': { + category: 'azure', + description: 'Ip address ', + name: 'azure.signinlogs.properties.ip_address', + type: 'keyword', + }, + 'azure.signinlogs.properties.client_app_used': { + category: 'azure', + description: 'Client app used ', + name: 'azure.signinlogs.properties.client_app_used', + type: 'keyword', + }, + 'azure.signinlogs.properties.conditional_access_status': { + category: 'azure', + description: 'Conditional access status ', + name: 'azure.signinlogs.properties.conditional_access_status', + type: 'keyword', + }, + 'azure.signinlogs.properties.original_request_id': { + category: 'azure', + description: 'Original request ID ', + name: 'azure.signinlogs.properties.original_request_id', + type: 'keyword', + }, + 'azure.signinlogs.properties.is_interactive': { + category: 'azure', + description: 'Is interactive ', + name: 'azure.signinlogs.properties.is_interactive', + type: 'keyword', + }, + 'azure.signinlogs.properties.token_issuer_name': { + category: 'azure', + description: 'Token issuer name ', + name: 'azure.signinlogs.properties.token_issuer_name', + type: 'keyword', + }, + 'azure.signinlogs.properties.token_issuer_type': { + category: 'azure', + description: 'Token issuer type ', + name: 'azure.signinlogs.properties.token_issuer_type', + type: 'keyword', + }, + 'azure.signinlogs.properties.processing_time_ms': { + category: 'azure', + description: 'Processing time in milliseconds ', + name: 'azure.signinlogs.properties.processing_time_ms', + type: 'float', + }, + 'azure.signinlogs.properties.risk_detail': { + category: 'azure', + description: 'Risk detail ', + name: 'azure.signinlogs.properties.risk_detail', + type: 'keyword', + }, + 'azure.signinlogs.properties.risk_level_aggregated': { + category: 'azure', + description: 'Risk level aggregated ', + name: 'azure.signinlogs.properties.risk_level_aggregated', + type: 'keyword', + }, + 'azure.signinlogs.properties.risk_level_during_signin': { + category: 'azure', + description: 'Risk level during signIn ', + name: 'azure.signinlogs.properties.risk_level_during_signin', + type: 'keyword', + }, + 'azure.signinlogs.properties.risk_state': { + category: 'azure', + description: 'Risk state ', + name: 'azure.signinlogs.properties.risk_state', + type: 'keyword', + }, + 'azure.signinlogs.properties.resource_display_name': { + category: 'azure', + description: 'Resource display name ', + name: 'azure.signinlogs.properties.resource_display_name', + type: 'keyword', + }, + 'azure.signinlogs.properties.status.error_code': { + category: 'azure', + description: 'Error code ', + name: 'azure.signinlogs.properties.status.error_code', + type: 'keyword', + }, + 'azure.signinlogs.properties.device_detail.device_id': { + category: 'azure', + description: 'Device ID ', + name: 'azure.signinlogs.properties.device_detail.device_id', + type: 'keyword', + }, + 'azure.signinlogs.properties.device_detail.operating_system': { + category: 'azure', + description: 'Operating system ', + name: 'azure.signinlogs.properties.device_detail.operating_system', + type: 'keyword', + }, + 'azure.signinlogs.properties.device_detail.browser': { + category: 'azure', + description: 'Browser ', + name: 'azure.signinlogs.properties.device_detail.browser', + type: 'keyword', + }, + 'azure.signinlogs.properties.device_detail.display_name': { + category: 'azure', + description: 'Display name ', + name: 'azure.signinlogs.properties.device_detail.display_name', + type: 'keyword', + }, + 'azure.signinlogs.properties.device_detail.trust_type': { + category: 'azure', + description: 'Trust type ', + name: 'azure.signinlogs.properties.device_detail.trust_type', + type: 'keyword', + }, + 'azure.signinlogs.properties.service_principal_id': { + category: 'azure', + description: 'Status ', + name: 'azure.signinlogs.properties.service_principal_id', + type: 'keyword', + }, + 'network.interface.name': { + category: 'network', + description: 'Name of the network interface where the traffic has been observed. ', + name: 'network.interface.name', + type: 'keyword', + }, + 'rsa.internal.msg': { + category: 'rsa', + description: 'This key is used to capture the raw message that comes into the Log Decoder', + name: 'rsa.internal.msg', + type: 'keyword', + }, + 'rsa.internal.messageid': { + category: 'rsa', + name: 'rsa.internal.messageid', + type: 'keyword', + }, + 'rsa.internal.event_desc': { + category: 'rsa', + name: 'rsa.internal.event_desc', + type: 'keyword', + }, + 'rsa.internal.message': { + category: 'rsa', + description: 'This key captures the contents of instant messages', + name: 'rsa.internal.message', + type: 'keyword', + }, + 'rsa.internal.time': { + category: 'rsa', + description: + 'This is the time at which a session hits a NetWitness Decoder. This key should never be used to parse Meta data from a session (Logs/Packets) Directly, this is a Reserved key in NetWitness.', + name: 'rsa.internal.time', + type: 'date', + }, + 'rsa.internal.level': { + category: 'rsa', + description: 'Deprecated key defined only in table map.', + name: 'rsa.internal.level', + type: 'long', + }, + 'rsa.internal.msg_id': { + category: 'rsa', + description: + 'This is the Message ID1 value that identifies the exact log parser definition which parses a particular log session. This key should never be used to parse Meta data from a session (Logs/Packets) Directly, this is a Reserved key in NetWitness', + name: 'rsa.internal.msg_id', + type: 'keyword', + }, + 'rsa.internal.msg_vid': { + category: 'rsa', + description: + 'This is the Message ID2 value that identifies the exact log parser definition which parses a particular log session. This key should never be used to parse Meta data from a session (Logs/Packets) Directly, this is a Reserved key in NetWitness', + name: 'rsa.internal.msg_vid', + type: 'keyword', + }, + 'rsa.internal.data': { + category: 'rsa', + description: 'Deprecated key defined only in table map.', + name: 'rsa.internal.data', + type: 'keyword', + }, + 'rsa.internal.obj_server': { + category: 'rsa', + description: 'Deprecated key defined only in table map.', + name: 'rsa.internal.obj_server', + type: 'keyword', + }, + 'rsa.internal.obj_val': { + category: 'rsa', + description: 'Deprecated key defined only in table map.', + name: 'rsa.internal.obj_val', + type: 'keyword', + }, + 'rsa.internal.resource': { + category: 'rsa', + description: 'Deprecated key defined only in table map.', + name: 'rsa.internal.resource', + type: 'keyword', + }, + 'rsa.internal.obj_id': { + category: 'rsa', + description: 'Deprecated key defined only in table map.', + name: 'rsa.internal.obj_id', + type: 'keyword', + }, + 'rsa.internal.statement': { + category: 'rsa', + description: 'Deprecated key defined only in table map.', + name: 'rsa.internal.statement', + type: 'keyword', + }, + 'rsa.internal.audit_class': { + category: 'rsa', + description: 'Deprecated key defined only in table map.', + name: 'rsa.internal.audit_class', + type: 'keyword', + }, + 'rsa.internal.entry': { + category: 'rsa', + description: 'Deprecated key defined only in table map.', + name: 'rsa.internal.entry', + type: 'keyword', + }, + 'rsa.internal.hcode': { + category: 'rsa', + description: 'Deprecated key defined only in table map.', + name: 'rsa.internal.hcode', + type: 'keyword', + }, + 'rsa.internal.inode': { + category: 'rsa', + description: 'Deprecated key defined only in table map.', + name: 'rsa.internal.inode', + type: 'long', + }, + 'rsa.internal.resource_class': { + category: 'rsa', + description: 'Deprecated key defined only in table map.', + name: 'rsa.internal.resource_class', + type: 'keyword', + }, + 'rsa.internal.dead': { + category: 'rsa', + description: 'Deprecated key defined only in table map.', + name: 'rsa.internal.dead', + type: 'long', + }, + 'rsa.internal.feed_desc': { + category: 'rsa', + description: + 'This is used to capture the description of the feed. This key should never be used to parse Meta data from a session (Logs/Packets) Directly, this is a Reserved key in NetWitness', + name: 'rsa.internal.feed_desc', + type: 'keyword', + }, + 'rsa.internal.feed_name': { + category: 'rsa', + description: + 'This is used to capture the name of the feed. This key should never be used to parse Meta data from a session (Logs/Packets) Directly, this is a Reserved key in NetWitness', + name: 'rsa.internal.feed_name', + type: 'keyword', + }, + 'rsa.internal.cid': { + category: 'rsa', + description: + 'This is the unique identifier used to identify a NetWitness Concentrator. This key should never be used to parse Meta data from a session (Logs/Packets) Directly, this is a Reserved key in NetWitness', + name: 'rsa.internal.cid', + type: 'keyword', + }, + 'rsa.internal.device_class': { + category: 'rsa', + description: + 'This is the Classification of the Log Event Source under a predefined fixed set of Event Source Classifications. This key should never be used to parse Meta data from a session (Logs/Packets) Directly, this is a Reserved key in NetWitness', + name: 'rsa.internal.device_class', + type: 'keyword', + }, + 'rsa.internal.device_group': { + category: 'rsa', + description: + 'This key should never be used to parse Meta data from a session (Logs/Packets) Directly, this is a Reserved key in NetWitness', + name: 'rsa.internal.device_group', + type: 'keyword', + }, + 'rsa.internal.device_host': { + category: 'rsa', + description: + 'This is the Hostname of the log Event Source sending the logs to NetWitness. This key should never be used to parse Meta data from a session (Logs/Packets) Directly, this is a Reserved key in NetWitness', + name: 'rsa.internal.device_host', + type: 'keyword', + }, + 'rsa.internal.device_ip': { + category: 'rsa', + description: + 'This is the IPv4 address of the Log Event Source sending the logs to NetWitness. This key should never be used to parse Meta data from a session (Logs/Packets) Directly, this is a Reserved key in NetWitness', + name: 'rsa.internal.device_ip', + type: 'ip', + }, + 'rsa.internal.device_ipv6': { + category: 'rsa', + description: + 'This is the IPv6 address of the Log Event Source sending the logs to NetWitness. This key should never be used to parse Meta data from a session (Logs/Packets) Directly, this is a Reserved key in NetWitness', + name: 'rsa.internal.device_ipv6', + type: 'ip', + }, + 'rsa.internal.device_type': { + category: 'rsa', + description: + 'This is the name of the log parser which parsed a given session. This key should never be used to parse Meta data from a session (Logs/Packets) Directly, this is a Reserved key in NetWitness', + name: 'rsa.internal.device_type', + type: 'keyword', + }, + 'rsa.internal.device_type_id': { + category: 'rsa', + description: 'Deprecated key defined only in table map.', + name: 'rsa.internal.device_type_id', + type: 'long', + }, + 'rsa.internal.did': { + category: 'rsa', + description: + 'This is the unique identifier used to identify a NetWitness Decoder. This key should never be used to parse Meta data from a session (Logs/Packets) Directly, this is a Reserved key in NetWitness', + name: 'rsa.internal.did', + type: 'keyword', + }, + 'rsa.internal.entropy_req': { + category: 'rsa', + description: + 'This key is only used by the Entropy Parser, the Meta Type can be either UInt16 or Float32 based on the configuration', + name: 'rsa.internal.entropy_req', + type: 'long', + }, + 'rsa.internal.entropy_res': { + category: 'rsa', + description: + 'This key is only used by the Entropy Parser, the Meta Type can be either UInt16 or Float32 based on the configuration', + name: 'rsa.internal.entropy_res', + type: 'long', + }, + 'rsa.internal.event_name': { + category: 'rsa', + description: 'Deprecated key defined only in table map.', + name: 'rsa.internal.event_name', + type: 'keyword', + }, + 'rsa.internal.feed_category': { + category: 'rsa', + description: + 'This is used to capture the category of the feed. This key should never be used to parse Meta data from a session (Logs/Packets) Directly, this is a Reserved key in NetWitness', + name: 'rsa.internal.feed_category', + type: 'keyword', + }, + 'rsa.internal.forward_ip': { + category: 'rsa', + description: + 'This key should be used to capture the IPV4 address of a relay system which forwarded the events from the original system to NetWitness.', + name: 'rsa.internal.forward_ip', + type: 'ip', + }, + 'rsa.internal.forward_ipv6': { + category: 'rsa', + description: + 'This key is used to capture the IPV6 address of a relay system which forwarded the events from the original system to NetWitness. This key should never be used to parse Meta data from a session (Logs/Packets) Directly, this is a Reserved key in NetWitness', + name: 'rsa.internal.forward_ipv6', + type: 'ip', + }, + 'rsa.internal.header_id': { + category: 'rsa', + description: + 'This is the Header ID value that identifies the exact log parser header definition that parses a particular log session. This key should never be used to parse Meta data from a session (Logs/Packets) Directly, this is a Reserved key in NetWitness', + name: 'rsa.internal.header_id', + type: 'keyword', + }, + 'rsa.internal.lc_cid': { + category: 'rsa', + description: + 'This is a unique Identifier of a Log Collector. This key should never be used to parse Meta data from a session (Logs/Packets) Directly, this is a Reserved key in NetWitness', + name: 'rsa.internal.lc_cid', + type: 'keyword', + }, + 'rsa.internal.lc_ctime': { + category: 'rsa', + description: + 'This is the time at which a log is collected in a NetWitness Log Collector. This key should never be used to parse Meta data from a session (Logs/Packets) Directly, this is a Reserved key in NetWitness', + name: 'rsa.internal.lc_ctime', + type: 'date', + }, + 'rsa.internal.mcb_req': { + category: 'rsa', + description: + 'This key is only used by the Entropy Parser, the most common byte request is simply which byte for each side (0 thru 255) was seen the most', + name: 'rsa.internal.mcb_req', + type: 'long', + }, + 'rsa.internal.mcb_res': { + category: 'rsa', + description: + 'This key is only used by the Entropy Parser, the most common byte response is simply which byte for each side (0 thru 255) was seen the most', + name: 'rsa.internal.mcb_res', + type: 'long', + }, + 'rsa.internal.mcbc_req': { + category: 'rsa', + description: + 'This key is only used by the Entropy Parser, the most common byte count is the number of times the most common byte (above) was seen in the session streams', + name: 'rsa.internal.mcbc_req', + type: 'long', + }, + 'rsa.internal.mcbc_res': { + category: 'rsa', + description: + 'This key is only used by the Entropy Parser, the most common byte count is the number of times the most common byte (above) was seen in the session streams', + name: 'rsa.internal.mcbc_res', + type: 'long', + }, + 'rsa.internal.medium': { + category: 'rsa', + description: + 'This key is used to identify if it’s a log/packet session or Layer 2 Encapsulation Type. This key should never be used to parse Meta data from a session (Logs/Packets) Directly, this is a Reserved key in NetWitness. 32 = log, 33 = correlation session, < 32 is packet session', + name: 'rsa.internal.medium', + type: 'long', + }, + 'rsa.internal.node_name': { + category: 'rsa', + description: 'Deprecated key defined only in table map.', + name: 'rsa.internal.node_name', + type: 'keyword', + }, + 'rsa.internal.nwe_callback_id': { + category: 'rsa', + description: 'This key denotes that event is endpoint related', + name: 'rsa.internal.nwe_callback_id', + type: 'keyword', + }, + 'rsa.internal.parse_error': { + category: 'rsa', + description: + 'This is a special key that stores any Meta key validation error found while parsing a log session. This key should never be used to parse Meta data from a session (Logs/Packets) Directly, this is a Reserved key in NetWitness', + name: 'rsa.internal.parse_error', + type: 'keyword', + }, + 'rsa.internal.payload_req': { + category: 'rsa', + description: + 'This key is only used by the Entropy Parser, the payload size metrics are the payload sizes of each session side at the time of parsing. However, in order to keep', + name: 'rsa.internal.payload_req', + type: 'long', + }, + 'rsa.internal.payload_res': { + category: 'rsa', + description: + 'This key is only used by the Entropy Parser, the payload size metrics are the payload sizes of each session side at the time of parsing. However, in order to keep', + name: 'rsa.internal.payload_res', + type: 'long', + }, + 'rsa.internal.process_vid_dst': { + category: 'rsa', + description: + 'Endpoint generates and uses a unique virtual ID to identify any similar group of process. This ID represents the target process.', + name: 'rsa.internal.process_vid_dst', + type: 'keyword', + }, + 'rsa.internal.process_vid_src': { + category: 'rsa', + description: + 'Endpoint generates and uses a unique virtual ID to identify any similar group of process. This ID represents the source process.', + name: 'rsa.internal.process_vid_src', + type: 'keyword', + }, + 'rsa.internal.rid': { + category: 'rsa', + description: + 'This is a special ID of the Remote Session created by NetWitness Decoder. This key should never be used to parse Meta data from a session (Logs/Packets) Directly, this is a Reserved key in NetWitness', + name: 'rsa.internal.rid', + type: 'long', + }, + 'rsa.internal.session_split': { + category: 'rsa', + description: + 'This key should never be used to parse Meta data from a session (Logs/Packets) Directly, this is a Reserved key in NetWitness', + name: 'rsa.internal.session_split', + type: 'keyword', + }, + 'rsa.internal.site': { + category: 'rsa', + description: 'Deprecated key defined only in table map.', + name: 'rsa.internal.site', + type: 'keyword', + }, + 'rsa.internal.size': { + category: 'rsa', + description: + 'This is the size of the session as seen by the NetWitness Decoder. This key should never be used to parse Meta data from a session (Logs/Packets) Directly, this is a Reserved key in NetWitness', + name: 'rsa.internal.size', + type: 'long', + }, + 'rsa.internal.sourcefile': { + category: 'rsa', + description: + 'This is the name of the log file or PCAPs that can be imported into NetWitness. This key should never be used to parse Meta data from a session (Logs/Packets) Directly, this is a Reserved key in NetWitness', + name: 'rsa.internal.sourcefile', + type: 'keyword', + }, + 'rsa.internal.ubc_req': { + category: 'rsa', + description: + 'This key is only used by the Entropy Parser, Unique byte count is the number of unique bytes seen in each stream. 256 would mean all byte values of 0 thru 255 were seen at least once', + name: 'rsa.internal.ubc_req', + type: 'long', + }, + 'rsa.internal.ubc_res': { + category: 'rsa', + description: + 'This key is only used by the Entropy Parser, Unique byte count is the number of unique bytes seen in each stream. 256 would mean all byte values of 0 thru 255 were seen at least once', + name: 'rsa.internal.ubc_res', + type: 'long', + }, + 'rsa.internal.word': { + category: 'rsa', + description: + 'This is used by the Word Parsing technology to capture the first 5 character of every word in an unparsed log', + name: 'rsa.internal.word', + type: 'keyword', + }, + 'rsa.time.event_time': { + category: 'rsa', + description: + 'This key is used to capture the time mentioned in a raw session that represents the actual time an event occured in a standard normalized form', + name: 'rsa.time.event_time', + type: 'date', + }, + 'rsa.time.duration_time': { + category: 'rsa', + description: 'This key is used to capture the normalized duration/lifetime in seconds.', + name: 'rsa.time.duration_time', + type: 'double', + }, + 'rsa.time.event_time_str': { + category: 'rsa', + description: + 'This key is used to capture the incomplete time mentioned in a session as a string', + name: 'rsa.time.event_time_str', + type: 'keyword', + }, + 'rsa.time.starttime': { + category: 'rsa', + description: + 'This key is used to capture the Start time mentioned in a session in a standard form', + name: 'rsa.time.starttime', + type: 'date', + }, + 'rsa.time.month': { + category: 'rsa', + name: 'rsa.time.month', + type: 'keyword', + }, + 'rsa.time.day': { + category: 'rsa', + name: 'rsa.time.day', + type: 'keyword', + }, + 'rsa.time.endtime': { + category: 'rsa', + description: + 'This key is used to capture the End time mentioned in a session in a standard form', + name: 'rsa.time.endtime', + type: 'date', + }, + 'rsa.time.timezone': { + category: 'rsa', + description: 'This key is used to capture the timezone of the Event Time', + name: 'rsa.time.timezone', + type: 'keyword', + }, + 'rsa.time.duration_str': { + category: 'rsa', + description: 'A text string version of the duration', + name: 'rsa.time.duration_str', + type: 'keyword', + }, + 'rsa.time.date': { + category: 'rsa', + name: 'rsa.time.date', + type: 'keyword', + }, + 'rsa.time.year': { + category: 'rsa', + name: 'rsa.time.year', + type: 'keyword', + }, + 'rsa.time.recorded_time': { + category: 'rsa', + description: + "The event time as recorded by the system the event is collected from. The usage scenario is a multi-tier application where the management layer of the system records it's own timestamp at the time of collection from its child nodes. Must be in timestamp format.", + name: 'rsa.time.recorded_time', + type: 'date', + }, + 'rsa.time.datetime': { + category: 'rsa', + name: 'rsa.time.datetime', + type: 'keyword', + }, + 'rsa.time.effective_time': { + category: 'rsa', + description: + 'This key is the effective time referenced by an individual event in a Standard Timestamp format', + name: 'rsa.time.effective_time', + type: 'date', + }, + 'rsa.time.expire_time': { + category: 'rsa', + description: 'This key is the timestamp that explicitly refers to an expiration.', + name: 'rsa.time.expire_time', + type: 'date', + }, + 'rsa.time.process_time': { + category: 'rsa', + description: 'Deprecated, use duration.time', + name: 'rsa.time.process_time', + type: 'keyword', + }, + 'rsa.time.hour': { + category: 'rsa', + name: 'rsa.time.hour', + type: 'keyword', + }, + 'rsa.time.min': { + category: 'rsa', + name: 'rsa.time.min', + type: 'keyword', + }, + 'rsa.time.timestamp': { + category: 'rsa', + name: 'rsa.time.timestamp', + type: 'keyword', + }, + 'rsa.time.event_queue_time': { + category: 'rsa', + description: 'This key is the Time that the event was queued.', + name: 'rsa.time.event_queue_time', + type: 'date', + }, + 'rsa.time.p_time1': { + category: 'rsa', + name: 'rsa.time.p_time1', + type: 'keyword', + }, + 'rsa.time.tzone': { + category: 'rsa', + name: 'rsa.time.tzone', + type: 'keyword', + }, + 'rsa.time.eventtime': { + category: 'rsa', + name: 'rsa.time.eventtime', + type: 'keyword', + }, + 'rsa.time.gmtdate': { + category: 'rsa', + name: 'rsa.time.gmtdate', + type: 'keyword', + }, + 'rsa.time.gmttime': { + category: 'rsa', + name: 'rsa.time.gmttime', + type: 'keyword', + }, + 'rsa.time.p_date': { + category: 'rsa', + name: 'rsa.time.p_date', + type: 'keyword', + }, + 'rsa.time.p_month': { + category: 'rsa', + name: 'rsa.time.p_month', + type: 'keyword', + }, + 'rsa.time.p_time': { + category: 'rsa', + name: 'rsa.time.p_time', + type: 'keyword', + }, + 'rsa.time.p_time2': { + category: 'rsa', + name: 'rsa.time.p_time2', + type: 'keyword', + }, + 'rsa.time.p_year': { + category: 'rsa', + name: 'rsa.time.p_year', + type: 'keyword', + }, + 'rsa.time.expire_time_str': { + category: 'rsa', + description: + 'This key is used to capture incomplete timestamp that explicitly refers to an expiration.', + name: 'rsa.time.expire_time_str', + type: 'keyword', + }, + 'rsa.time.stamp': { + category: 'rsa', + description: 'Deprecated key defined only in table map.', + name: 'rsa.time.stamp', + type: 'date', + }, + 'rsa.misc.action': { + category: 'rsa', + name: 'rsa.misc.action', + type: 'keyword', + }, + 'rsa.misc.result': { + category: 'rsa', + description: + 'This key is used to capture the outcome/result string value of an action in a session.', + name: 'rsa.misc.result', + type: 'keyword', + }, + 'rsa.misc.severity': { + category: 'rsa', + description: 'This key is used to capture the severity given the session', + name: 'rsa.misc.severity', + type: 'keyword', + }, + 'rsa.misc.event_type': { + category: 'rsa', + description: 'This key captures the event category type as specified by the event source.', + name: 'rsa.misc.event_type', + type: 'keyword', + }, + 'rsa.misc.reference_id': { + category: 'rsa', + description: 'This key is used to capture an event id from the session directly', + name: 'rsa.misc.reference_id', + type: 'keyword', + }, + 'rsa.misc.version': { + category: 'rsa', + description: + 'This key captures Version of the application or OS which is generating the event.', + name: 'rsa.misc.version', + type: 'keyword', + }, + 'rsa.misc.disposition': { + category: 'rsa', + description: 'This key captures the The end state of an action.', + name: 'rsa.misc.disposition', + type: 'keyword', + }, + 'rsa.misc.result_code': { + category: 'rsa', + description: + 'This key is used to capture the outcome/result numeric value of an action in a session', + name: 'rsa.misc.result_code', + type: 'keyword', + }, + 'rsa.misc.category': { + category: 'rsa', + description: + 'This key is used to capture the category of an event given by the vendor in the session', + name: 'rsa.misc.category', + type: 'keyword', + }, + 'rsa.misc.obj_name': { + category: 'rsa', + description: 'This is used to capture name of object', + name: 'rsa.misc.obj_name', + type: 'keyword', + }, + 'rsa.misc.obj_type': { + category: 'rsa', + description: 'This is used to capture type of object', + name: 'rsa.misc.obj_type', + type: 'keyword', + }, + 'rsa.misc.event_source': { + category: 'rsa', + description: 'This key captures Source of the event that’s not a hostname', + name: 'rsa.misc.event_source', + type: 'keyword', + }, + 'rsa.misc.log_session_id': { + category: 'rsa', + description: 'This key is used to capture a sessionid from the session directly', + name: 'rsa.misc.log_session_id', + type: 'keyword', + }, + 'rsa.misc.group': { + category: 'rsa', + description: 'This key captures the Group Name value', + name: 'rsa.misc.group', + type: 'keyword', + }, + 'rsa.misc.policy_name': { + category: 'rsa', + description: 'This key is used to capture the Policy Name only.', + name: 'rsa.misc.policy_name', + type: 'keyword', + }, + 'rsa.misc.rule_name': { + category: 'rsa', + description: 'This key captures the Rule Name', + name: 'rsa.misc.rule_name', + type: 'keyword', + }, + 'rsa.misc.context': { + category: 'rsa', + description: 'This key captures Information which adds additional context to the event.', + name: 'rsa.misc.context', + type: 'keyword', + }, + 'rsa.misc.change_new': { + category: 'rsa', + description: + 'This key is used to capture the new values of the attribute that’s changing in a session', + name: 'rsa.misc.change_new', + type: 'keyword', + }, + 'rsa.misc.space': { + category: 'rsa', + name: 'rsa.misc.space', + type: 'keyword', + }, + 'rsa.misc.client': { + category: 'rsa', + description: + 'This key is used to capture only the name of the client application requesting resources of the server. See the user.agent meta key for capture of the specific user agent identifier or browser identification string.', + name: 'rsa.misc.client', + type: 'keyword', + }, + 'rsa.misc.msgIdPart1': { + category: 'rsa', + name: 'rsa.misc.msgIdPart1', + type: 'keyword', + }, + 'rsa.misc.msgIdPart2': { + category: 'rsa', + name: 'rsa.misc.msgIdPart2', + type: 'keyword', + }, + 'rsa.misc.change_old': { + category: 'rsa', + description: + 'This key is used to capture the old value of the attribute that’s changing in a session', + name: 'rsa.misc.change_old', + type: 'keyword', + }, + 'rsa.misc.operation_id': { + category: 'rsa', + description: + 'An alert number or operation number. The values should be unique and non-repeating.', + name: 'rsa.misc.operation_id', + type: 'keyword', + }, + 'rsa.misc.event_state': { + category: 'rsa', + description: + 'This key captures the current state of the object/item referenced within the event. Describing an on-going event.', + name: 'rsa.misc.event_state', + type: 'keyword', + }, + 'rsa.misc.group_object': { + category: 'rsa', + description: 'This key captures a collection/grouping of entities. Specific usage', + name: 'rsa.misc.group_object', + type: 'keyword', + }, + 'rsa.misc.node': { + category: 'rsa', + description: + 'Common use case is the node name within a cluster. The cluster name is reflected by the host name.', + name: 'rsa.misc.node', + type: 'keyword', + }, + 'rsa.misc.rule': { + category: 'rsa', + description: 'This key captures the Rule number', + name: 'rsa.misc.rule', + type: 'keyword', + }, + 'rsa.misc.device_name': { + category: 'rsa', + description: + 'This is used to capture name of the Device associated with the node Like: a physical disk, printer, etc', + name: 'rsa.misc.device_name', + type: 'keyword', + }, + 'rsa.misc.param': { + category: 'rsa', + description: 'This key is the parameters passed as part of a command or application, etc.', + name: 'rsa.misc.param', + type: 'keyword', + }, + 'rsa.misc.change_attrib': { + category: 'rsa', + description: + 'This key is used to capture the name of the attribute that’s changing in a session', + name: 'rsa.misc.change_attrib', + type: 'keyword', + }, + 'rsa.misc.event_computer': { + category: 'rsa', + description: + 'This key is a windows only concept, where this key is used to capture fully qualified domain name in a windows log.', + name: 'rsa.misc.event_computer', + type: 'keyword', + }, + 'rsa.misc.reference_id1': { + category: 'rsa', + description: 'This key is for Linked ID to be used as an addition to "reference.id"', + name: 'rsa.misc.reference_id1', + type: 'keyword', + }, + 'rsa.misc.event_log': { + category: 'rsa', + description: 'This key captures the Name of the event log', + name: 'rsa.misc.event_log', + type: 'keyword', + }, + 'rsa.misc.OS': { + category: 'rsa', + description: 'This key captures the Name of the Operating System', + name: 'rsa.misc.OS', + type: 'keyword', + }, + 'rsa.misc.terminal': { + category: 'rsa', + description: 'This key captures the Terminal Names only', + name: 'rsa.misc.terminal', + type: 'keyword', + }, + 'rsa.misc.msgIdPart3': { + category: 'rsa', + name: 'rsa.misc.msgIdPart3', + type: 'keyword', + }, + 'rsa.misc.filter': { + category: 'rsa', + description: 'This key captures Filter used to reduce result set', + name: 'rsa.misc.filter', + type: 'keyword', + }, + 'rsa.misc.serial_number': { + category: 'rsa', + description: 'This key is the Serial number associated with a physical asset.', + name: 'rsa.misc.serial_number', + type: 'keyword', + }, + 'rsa.misc.checksum': { + category: 'rsa', + description: + 'This key is used to capture the checksum or hash of the entity such as a file or process. Checksum should be used over checksum.src or checksum.dst when it is unclear whether the entity is a source or target of an action.', + name: 'rsa.misc.checksum', + type: 'keyword', + }, + 'rsa.misc.event_user': { + category: 'rsa', + description: + 'This key is a windows only concept, where this key is used to capture combination of domain name and username in a windows log.', + name: 'rsa.misc.event_user', + type: 'keyword', + }, + 'rsa.misc.virusname': { + category: 'rsa', + description: 'This key captures the name of the virus', + name: 'rsa.misc.virusname', + type: 'keyword', + }, + 'rsa.misc.content_type': { + category: 'rsa', + description: 'This key is used to capture Content Type only.', + name: 'rsa.misc.content_type', + type: 'keyword', + }, + 'rsa.misc.group_id': { + category: 'rsa', + description: 'This key captures Group ID Number (related to the group name)', + name: 'rsa.misc.group_id', + type: 'keyword', + }, + 'rsa.misc.policy_id': { + category: 'rsa', + description: + 'This key is used to capture the Policy ID only, this should be a numeric value, use policy.name otherwise', + name: 'rsa.misc.policy_id', + type: 'keyword', + }, + 'rsa.misc.vsys': { + category: 'rsa', + description: 'This key captures Virtual System Name', + name: 'rsa.misc.vsys', + type: 'keyword', + }, + 'rsa.misc.connection_id': { + category: 'rsa', + description: 'This key captures the Connection ID', + name: 'rsa.misc.connection_id', + type: 'keyword', + }, + 'rsa.misc.reference_id2': { + category: 'rsa', + description: + 'This key is for the 2nd Linked ID. Can be either linked to "reference.id" or "reference.id1" value but should not be used unless the other two variables are in play.', + name: 'rsa.misc.reference_id2', + type: 'keyword', + }, + 'rsa.misc.sensor': { + category: 'rsa', + description: 'This key captures Name of the sensor. Typically used in IDS/IPS based devices', + name: 'rsa.misc.sensor', + type: 'keyword', + }, + 'rsa.misc.sig_id': { + category: 'rsa', + description: 'This key captures IDS/IPS Int Signature ID', + name: 'rsa.misc.sig_id', + type: 'long', + }, + 'rsa.misc.port_name': { + category: 'rsa', + description: + 'This key is used for Physical or logical port connection but does NOT include a network port. (Example: Printer port name).', + name: 'rsa.misc.port_name', + type: 'keyword', + }, + 'rsa.misc.rule_group': { + category: 'rsa', + description: 'This key captures the Rule group name', + name: 'rsa.misc.rule_group', + type: 'keyword', + }, + 'rsa.misc.risk_num': { + category: 'rsa', + description: 'This key captures a Numeric Risk value', + name: 'rsa.misc.risk_num', + type: 'double', + }, + 'rsa.misc.trigger_val': { + category: 'rsa', + description: 'This key captures the Value of the trigger or threshold condition.', + name: 'rsa.misc.trigger_val', + type: 'keyword', + }, + 'rsa.misc.log_session_id1': { + category: 'rsa', + description: + 'This key is used to capture a Linked (Related) Session ID from the session directly', + name: 'rsa.misc.log_session_id1', + type: 'keyword', + }, + 'rsa.misc.comp_version': { + category: 'rsa', + description: 'This key captures the Version level of a sub-component of a product.', + name: 'rsa.misc.comp_version', + type: 'keyword', + }, + 'rsa.misc.content_version': { + category: 'rsa', + description: 'This key captures Version level of a signature or database content.', + name: 'rsa.misc.content_version', + type: 'keyword', + }, + 'rsa.misc.hardware_id': { + category: 'rsa', + description: + 'This key is used to capture unique identifier for a device or system (NOT a Mac address)', + name: 'rsa.misc.hardware_id', + type: 'keyword', + }, + 'rsa.misc.risk': { + category: 'rsa', + description: 'This key captures the non-numeric risk value', + name: 'rsa.misc.risk', + type: 'keyword', + }, + 'rsa.misc.event_id': { + category: 'rsa', + name: 'rsa.misc.event_id', + type: 'keyword', + }, + 'rsa.misc.reason': { + category: 'rsa', + name: 'rsa.misc.reason', + type: 'keyword', + }, + 'rsa.misc.status': { + category: 'rsa', + name: 'rsa.misc.status', + type: 'keyword', + }, + 'rsa.misc.mail_id': { + category: 'rsa', + description: 'This key is used to capture the mailbox id/name', + name: 'rsa.misc.mail_id', + type: 'keyword', + }, + 'rsa.misc.rule_uid': { + category: 'rsa', + description: 'This key is the Unique Identifier for a rule.', + name: 'rsa.misc.rule_uid', + type: 'keyword', + }, + 'rsa.misc.trigger_desc': { + category: 'rsa', + description: 'This key captures the Description of the trigger or threshold condition.', + name: 'rsa.misc.trigger_desc', + type: 'keyword', + }, + 'rsa.misc.inout': { + category: 'rsa', + name: 'rsa.misc.inout', + type: 'keyword', + }, + 'rsa.misc.p_msgid': { + category: 'rsa', + name: 'rsa.misc.p_msgid', + type: 'keyword', + }, + 'rsa.misc.data_type': { + category: 'rsa', + name: 'rsa.misc.data_type', + type: 'keyword', + }, + 'rsa.misc.msgIdPart4': { + category: 'rsa', + name: 'rsa.misc.msgIdPart4', + type: 'keyword', + }, + 'rsa.misc.error': { + category: 'rsa', + description: 'This key captures All non successful Error codes or responses', + name: 'rsa.misc.error', + type: 'keyword', + }, + 'rsa.misc.index': { + category: 'rsa', + name: 'rsa.misc.index', + type: 'keyword', + }, + 'rsa.misc.listnum': { + category: 'rsa', + description: + 'This key is used to capture listname or listnumber, primarily for collecting access-list', + name: 'rsa.misc.listnum', + type: 'keyword', + }, + 'rsa.misc.ntype': { + category: 'rsa', + name: 'rsa.misc.ntype', + type: 'keyword', + }, + 'rsa.misc.observed_val': { + category: 'rsa', + description: + 'This key captures the Value observed (from the perspective of the device generating the log).', + name: 'rsa.misc.observed_val', + type: 'keyword', + }, + 'rsa.misc.policy_value': { + category: 'rsa', + description: + 'This key captures the contents of the policy. This contains details about the policy', + name: 'rsa.misc.policy_value', + type: 'keyword', + }, + 'rsa.misc.pool_name': { + category: 'rsa', + description: 'This key captures the name of a resource pool', + name: 'rsa.misc.pool_name', + type: 'keyword', + }, + 'rsa.misc.rule_template': { + category: 'rsa', + description: + 'A default set of parameters which are overlayed onto a rule (or rulename) which efffectively constitutes a template', + name: 'rsa.misc.rule_template', + type: 'keyword', + }, + 'rsa.misc.count': { + category: 'rsa', + name: 'rsa.misc.count', + type: 'keyword', + }, + 'rsa.misc.number': { + category: 'rsa', + name: 'rsa.misc.number', + type: 'keyword', + }, + 'rsa.misc.sigcat': { + category: 'rsa', + name: 'rsa.misc.sigcat', + type: 'keyword', + }, + 'rsa.misc.type': { + category: 'rsa', + name: 'rsa.misc.type', + type: 'keyword', + }, + 'rsa.misc.comments': { + category: 'rsa', + description: 'Comment information provided in the log message', + name: 'rsa.misc.comments', + type: 'keyword', + }, + 'rsa.misc.doc_number': { + category: 'rsa', + description: 'This key captures File Identification number', + name: 'rsa.misc.doc_number', + type: 'long', + }, + 'rsa.misc.expected_val': { + category: 'rsa', + description: + 'This key captures the Value expected (from the perspective of the device generating the log).', + name: 'rsa.misc.expected_val', + type: 'keyword', + }, + 'rsa.misc.job_num': { + category: 'rsa', + description: 'This key captures the Job Number', + name: 'rsa.misc.job_num', + type: 'keyword', + }, + 'rsa.misc.spi_dst': { + category: 'rsa', + description: 'Destination SPI Index', + name: 'rsa.misc.spi_dst', + type: 'keyword', + }, + 'rsa.misc.spi_src': { + category: 'rsa', + description: 'Source SPI Index', + name: 'rsa.misc.spi_src', + type: 'keyword', + }, + 'rsa.misc.code': { + category: 'rsa', + name: 'rsa.misc.code', + type: 'keyword', + }, + 'rsa.misc.agent_id': { + category: 'rsa', + description: 'This key is used to capture agent id', + name: 'rsa.misc.agent_id', + type: 'keyword', + }, + 'rsa.misc.message_body': { + category: 'rsa', + description: 'This key captures the The contents of the message body.', + name: 'rsa.misc.message_body', + type: 'keyword', + }, + 'rsa.misc.phone': { + category: 'rsa', + name: 'rsa.misc.phone', + type: 'keyword', + }, + 'rsa.misc.sig_id_str': { + category: 'rsa', + description: 'This key captures a string object of the sigid variable.', + name: 'rsa.misc.sig_id_str', + type: 'keyword', + }, + 'rsa.misc.cmd': { + category: 'rsa', + name: 'rsa.misc.cmd', + type: 'keyword', + }, + 'rsa.misc.misc': { + category: 'rsa', + name: 'rsa.misc.misc', + type: 'keyword', + }, + 'rsa.misc.name': { + category: 'rsa', + name: 'rsa.misc.name', + type: 'keyword', + }, + 'rsa.misc.cpu': { + category: 'rsa', + description: 'This key is the CPU time used in the execution of the event being recorded.', + name: 'rsa.misc.cpu', + type: 'long', + }, + 'rsa.misc.event_desc': { + category: 'rsa', + description: + 'This key is used to capture a description of an event available directly or inferred', + name: 'rsa.misc.event_desc', + type: 'keyword', + }, + 'rsa.misc.sig_id1': { + category: 'rsa', + description: 'This key captures IDS/IPS Int Signature ID. This must be linked to the sig.id', + name: 'rsa.misc.sig_id1', + type: 'long', + }, + 'rsa.misc.im_buddyid': { + category: 'rsa', + name: 'rsa.misc.im_buddyid', + type: 'keyword', + }, + 'rsa.misc.im_client': { + category: 'rsa', + name: 'rsa.misc.im_client', + type: 'keyword', + }, + 'rsa.misc.im_userid': { + category: 'rsa', + name: 'rsa.misc.im_userid', + type: 'keyword', + }, + 'rsa.misc.pid': { + category: 'rsa', + name: 'rsa.misc.pid', + type: 'keyword', + }, + 'rsa.misc.priority': { + category: 'rsa', + name: 'rsa.misc.priority', + type: 'keyword', + }, + 'rsa.misc.context_subject': { + category: 'rsa', + description: + 'This key is to be used in an audit context where the subject is the object being identified', + name: 'rsa.misc.context_subject', + type: 'keyword', + }, + 'rsa.misc.context_target': { + category: 'rsa', + name: 'rsa.misc.context_target', + type: 'keyword', + }, + 'rsa.misc.cve': { + category: 'rsa', + description: + 'This key captures CVE (Common Vulnerabilities and Exposures) - an identifier for known information security vulnerabilities.', + name: 'rsa.misc.cve', + type: 'keyword', + }, + 'rsa.misc.fcatnum': { + category: 'rsa', + description: 'This key captures Filter Category Number. Legacy Usage', + name: 'rsa.misc.fcatnum', + type: 'keyword', + }, + 'rsa.misc.library': { + category: 'rsa', + description: 'This key is used to capture library information in mainframe devices', + name: 'rsa.misc.library', + type: 'keyword', + }, + 'rsa.misc.parent_node': { + category: 'rsa', + description: 'This key captures the Parent Node Name. Must be related to node variable.', + name: 'rsa.misc.parent_node', + type: 'keyword', + }, + 'rsa.misc.risk_info': { + category: 'rsa', + description: 'Deprecated, use New Hunting Model (inv.*, ioc, boc, eoc, analysis.*)', + name: 'rsa.misc.risk_info', + type: 'keyword', + }, + 'rsa.misc.tcp_flags': { + category: 'rsa', + description: 'This key is captures the TCP flags set in any packet of session', + name: 'rsa.misc.tcp_flags', + type: 'long', + }, + 'rsa.misc.tos': { + category: 'rsa', + description: 'This key describes the type of service', + name: 'rsa.misc.tos', + type: 'long', + }, + 'rsa.misc.vm_target': { + category: 'rsa', + description: 'VMWare Target **VMWARE** only varaible.', + name: 'rsa.misc.vm_target', + type: 'keyword', + }, + 'rsa.misc.workspace': { + category: 'rsa', + description: 'This key captures Workspace Description', + name: 'rsa.misc.workspace', + type: 'keyword', + }, + 'rsa.misc.command': { + category: 'rsa', + name: 'rsa.misc.command', + type: 'keyword', + }, + 'rsa.misc.event_category': { + category: 'rsa', + name: 'rsa.misc.event_category', + type: 'keyword', + }, + 'rsa.misc.facilityname': { + category: 'rsa', + name: 'rsa.misc.facilityname', + type: 'keyword', + }, + 'rsa.misc.forensic_info': { + category: 'rsa', + name: 'rsa.misc.forensic_info', + type: 'keyword', + }, + 'rsa.misc.jobname': { + category: 'rsa', + name: 'rsa.misc.jobname', + type: 'keyword', + }, + 'rsa.misc.mode': { + category: 'rsa', + name: 'rsa.misc.mode', + type: 'keyword', + }, + 'rsa.misc.policy': { + category: 'rsa', + name: 'rsa.misc.policy', + type: 'keyword', + }, + 'rsa.misc.policy_waiver': { + category: 'rsa', + name: 'rsa.misc.policy_waiver', + type: 'keyword', + }, + 'rsa.misc.second': { + category: 'rsa', + name: 'rsa.misc.second', + type: 'keyword', + }, + 'rsa.misc.space1': { + category: 'rsa', + name: 'rsa.misc.space1', + type: 'keyword', + }, + 'rsa.misc.subcategory': { + category: 'rsa', + name: 'rsa.misc.subcategory', + type: 'keyword', + }, + 'rsa.misc.tbdstr2': { + category: 'rsa', + name: 'rsa.misc.tbdstr2', + type: 'keyword', + }, + 'rsa.misc.alert_id': { + category: 'rsa', + description: 'Deprecated, New Hunting Model (inv.*, ioc, boc, eoc, analysis.*)', + name: 'rsa.misc.alert_id', + type: 'keyword', + }, + 'rsa.misc.checksum_dst': { + category: 'rsa', + description: + 'This key is used to capture the checksum or hash of the the target entity such as a process or file.', + name: 'rsa.misc.checksum_dst', + type: 'keyword', + }, + 'rsa.misc.checksum_src': { + category: 'rsa', + description: + 'This key is used to capture the checksum or hash of the source entity such as a file or process.', + name: 'rsa.misc.checksum_src', + type: 'keyword', + }, + 'rsa.misc.fresult': { + category: 'rsa', + description: 'This key captures the Filter Result', + name: 'rsa.misc.fresult', + type: 'long', + }, + 'rsa.misc.payload_dst': { + category: 'rsa', + description: 'This key is used to capture destination payload', + name: 'rsa.misc.payload_dst', + type: 'keyword', + }, + 'rsa.misc.payload_src': { + category: 'rsa', + description: 'This key is used to capture source payload', + name: 'rsa.misc.payload_src', + type: 'keyword', + }, + 'rsa.misc.pool_id': { + category: 'rsa', + description: 'This key captures the identifier (typically numeric field) of a resource pool', + name: 'rsa.misc.pool_id', + type: 'keyword', + }, + 'rsa.misc.process_id_val': { + category: 'rsa', + description: 'This key is a failure key for Process ID when it is not an integer value', + name: 'rsa.misc.process_id_val', + type: 'keyword', + }, + 'rsa.misc.risk_num_comm': { + category: 'rsa', + description: 'This key captures Risk Number Community', + name: 'rsa.misc.risk_num_comm', + type: 'double', + }, + 'rsa.misc.risk_num_next': { + category: 'rsa', + description: 'This key captures Risk Number NextGen', + name: 'rsa.misc.risk_num_next', + type: 'double', + }, + 'rsa.misc.risk_num_sand': { + category: 'rsa', + description: 'This key captures Risk Number SandBox', + name: 'rsa.misc.risk_num_sand', + type: 'double', + }, + 'rsa.misc.risk_num_static': { + category: 'rsa', + description: 'This key captures Risk Number Static', + name: 'rsa.misc.risk_num_static', + type: 'double', + }, + 'rsa.misc.risk_suspicious': { + category: 'rsa', + description: 'Deprecated, use New Hunting Model (inv.*, ioc, boc, eoc, analysis.*)', + name: 'rsa.misc.risk_suspicious', + type: 'keyword', + }, + 'rsa.misc.risk_warning': { + category: 'rsa', + description: 'Deprecated, use New Hunting Model (inv.*, ioc, boc, eoc, analysis.*)', + name: 'rsa.misc.risk_warning', + type: 'keyword', + }, + 'rsa.misc.snmp_oid': { + category: 'rsa', + description: 'SNMP Object Identifier', + name: 'rsa.misc.snmp_oid', + type: 'keyword', + }, + 'rsa.misc.sql': { + category: 'rsa', + description: 'This key captures the SQL query', + name: 'rsa.misc.sql', + type: 'keyword', + }, + 'rsa.misc.vuln_ref': { + category: 'rsa', + description: 'This key captures the Vulnerability Reference details', + name: 'rsa.misc.vuln_ref', + type: 'keyword', + }, + 'rsa.misc.acl_id': { + category: 'rsa', + name: 'rsa.misc.acl_id', + type: 'keyword', + }, + 'rsa.misc.acl_op': { + category: 'rsa', + name: 'rsa.misc.acl_op', + type: 'keyword', + }, + 'rsa.misc.acl_pos': { + category: 'rsa', + name: 'rsa.misc.acl_pos', + type: 'keyword', + }, + 'rsa.misc.acl_table': { + category: 'rsa', + name: 'rsa.misc.acl_table', + type: 'keyword', + }, + 'rsa.misc.admin': { + category: 'rsa', + name: 'rsa.misc.admin', + type: 'keyword', + }, + 'rsa.misc.alarm_id': { + category: 'rsa', + name: 'rsa.misc.alarm_id', + type: 'keyword', + }, + 'rsa.misc.alarmname': { + category: 'rsa', + name: 'rsa.misc.alarmname', + type: 'keyword', + }, + 'rsa.misc.app_id': { + category: 'rsa', + name: 'rsa.misc.app_id', + type: 'keyword', + }, + 'rsa.misc.audit': { + category: 'rsa', + name: 'rsa.misc.audit', + type: 'keyword', + }, + 'rsa.misc.audit_object': { + category: 'rsa', + name: 'rsa.misc.audit_object', + type: 'keyword', + }, + 'rsa.misc.auditdata': { + category: 'rsa', + name: 'rsa.misc.auditdata', + type: 'keyword', + }, + 'rsa.misc.benchmark': { + category: 'rsa', + name: 'rsa.misc.benchmark', + type: 'keyword', + }, + 'rsa.misc.bypass': { + category: 'rsa', + name: 'rsa.misc.bypass', + type: 'keyword', + }, + 'rsa.misc.cache': { + category: 'rsa', + name: 'rsa.misc.cache', + type: 'keyword', + }, + 'rsa.misc.cache_hit': { + category: 'rsa', + name: 'rsa.misc.cache_hit', + type: 'keyword', + }, + 'rsa.misc.cefversion': { + category: 'rsa', + name: 'rsa.misc.cefversion', + type: 'keyword', + }, + 'rsa.misc.cfg_attr': { + category: 'rsa', + name: 'rsa.misc.cfg_attr', + type: 'keyword', + }, + 'rsa.misc.cfg_obj': { + category: 'rsa', + name: 'rsa.misc.cfg_obj', + type: 'keyword', + }, + 'rsa.misc.cfg_path': { + category: 'rsa', + name: 'rsa.misc.cfg_path', + type: 'keyword', + }, + 'rsa.misc.changes': { + category: 'rsa', + name: 'rsa.misc.changes', + type: 'keyword', + }, + 'rsa.misc.client_ip': { + category: 'rsa', + name: 'rsa.misc.client_ip', + type: 'keyword', + }, + 'rsa.misc.clustermembers': { + category: 'rsa', + name: 'rsa.misc.clustermembers', + type: 'keyword', + }, + 'rsa.misc.cn_acttimeout': { + category: 'rsa', + name: 'rsa.misc.cn_acttimeout', + type: 'keyword', + }, + 'rsa.misc.cn_asn_src': { + category: 'rsa', + name: 'rsa.misc.cn_asn_src', + type: 'keyword', + }, + 'rsa.misc.cn_bgpv4nxthop': { + category: 'rsa', + name: 'rsa.misc.cn_bgpv4nxthop', + type: 'keyword', + }, + 'rsa.misc.cn_ctr_dst_code': { + category: 'rsa', + name: 'rsa.misc.cn_ctr_dst_code', + type: 'keyword', + }, + 'rsa.misc.cn_dst_tos': { + category: 'rsa', + name: 'rsa.misc.cn_dst_tos', + type: 'keyword', + }, + 'rsa.misc.cn_dst_vlan': { + category: 'rsa', + name: 'rsa.misc.cn_dst_vlan', + type: 'keyword', + }, + 'rsa.misc.cn_engine_id': { + category: 'rsa', + name: 'rsa.misc.cn_engine_id', + type: 'keyword', + }, + 'rsa.misc.cn_engine_type': { + category: 'rsa', + name: 'rsa.misc.cn_engine_type', + type: 'keyword', + }, + 'rsa.misc.cn_f_switch': { + category: 'rsa', + name: 'rsa.misc.cn_f_switch', + type: 'keyword', + }, + 'rsa.misc.cn_flowsampid': { + category: 'rsa', + name: 'rsa.misc.cn_flowsampid', + type: 'keyword', + }, + 'rsa.misc.cn_flowsampintv': { + category: 'rsa', + name: 'rsa.misc.cn_flowsampintv', + type: 'keyword', + }, + 'rsa.misc.cn_flowsampmode': { + category: 'rsa', + name: 'rsa.misc.cn_flowsampmode', + type: 'keyword', + }, + 'rsa.misc.cn_inacttimeout': { + category: 'rsa', + name: 'rsa.misc.cn_inacttimeout', + type: 'keyword', + }, + 'rsa.misc.cn_inpermbyts': { + category: 'rsa', + name: 'rsa.misc.cn_inpermbyts', + type: 'keyword', + }, + 'rsa.misc.cn_inpermpckts': { + category: 'rsa', + name: 'rsa.misc.cn_inpermpckts', + type: 'keyword', + }, + 'rsa.misc.cn_invalid': { + category: 'rsa', + name: 'rsa.misc.cn_invalid', + type: 'keyword', + }, + 'rsa.misc.cn_ip_proto_ver': { + category: 'rsa', + name: 'rsa.misc.cn_ip_proto_ver', + type: 'keyword', + }, + 'rsa.misc.cn_ipv4_ident': { + category: 'rsa', + name: 'rsa.misc.cn_ipv4_ident', + type: 'keyword', + }, + 'rsa.misc.cn_l_switch': { + category: 'rsa', + name: 'rsa.misc.cn_l_switch', + type: 'keyword', + }, + 'rsa.misc.cn_log_did': { + category: 'rsa', + name: 'rsa.misc.cn_log_did', + type: 'keyword', + }, + 'rsa.misc.cn_log_rid': { + category: 'rsa', + name: 'rsa.misc.cn_log_rid', + type: 'keyword', + }, + 'rsa.misc.cn_max_ttl': { + category: 'rsa', + name: 'rsa.misc.cn_max_ttl', + type: 'keyword', + }, + 'rsa.misc.cn_maxpcktlen': { + category: 'rsa', + name: 'rsa.misc.cn_maxpcktlen', + type: 'keyword', + }, + 'rsa.misc.cn_min_ttl': { + category: 'rsa', + name: 'rsa.misc.cn_min_ttl', + type: 'keyword', + }, + 'rsa.misc.cn_minpcktlen': { + category: 'rsa', + name: 'rsa.misc.cn_minpcktlen', + type: 'keyword', + }, + 'rsa.misc.cn_mpls_lbl_1': { + category: 'rsa', + name: 'rsa.misc.cn_mpls_lbl_1', + type: 'keyword', + }, + 'rsa.misc.cn_mpls_lbl_10': { + category: 'rsa', + name: 'rsa.misc.cn_mpls_lbl_10', + type: 'keyword', + }, + 'rsa.misc.cn_mpls_lbl_2': { + category: 'rsa', + name: 'rsa.misc.cn_mpls_lbl_2', + type: 'keyword', + }, + 'rsa.misc.cn_mpls_lbl_3': { + category: 'rsa', + name: 'rsa.misc.cn_mpls_lbl_3', + type: 'keyword', + }, + 'rsa.misc.cn_mpls_lbl_4': { + category: 'rsa', + name: 'rsa.misc.cn_mpls_lbl_4', + type: 'keyword', + }, + 'rsa.misc.cn_mpls_lbl_5': { + category: 'rsa', + name: 'rsa.misc.cn_mpls_lbl_5', + type: 'keyword', + }, + 'rsa.misc.cn_mpls_lbl_6': { + category: 'rsa', + name: 'rsa.misc.cn_mpls_lbl_6', + type: 'keyword', + }, + 'rsa.misc.cn_mpls_lbl_7': { + category: 'rsa', + name: 'rsa.misc.cn_mpls_lbl_7', + type: 'keyword', + }, + 'rsa.misc.cn_mpls_lbl_8': { + category: 'rsa', + name: 'rsa.misc.cn_mpls_lbl_8', + type: 'keyword', + }, + 'rsa.misc.cn_mpls_lbl_9': { + category: 'rsa', + name: 'rsa.misc.cn_mpls_lbl_9', + type: 'keyword', + }, + 'rsa.misc.cn_mplstoplabel': { + category: 'rsa', + name: 'rsa.misc.cn_mplstoplabel', + type: 'keyword', + }, + 'rsa.misc.cn_mplstoplabip': { + category: 'rsa', + name: 'rsa.misc.cn_mplstoplabip', + type: 'keyword', + }, + 'rsa.misc.cn_mul_dst_byt': { + category: 'rsa', + name: 'rsa.misc.cn_mul_dst_byt', + type: 'keyword', + }, + 'rsa.misc.cn_mul_dst_pks': { + category: 'rsa', + name: 'rsa.misc.cn_mul_dst_pks', + type: 'keyword', + }, + 'rsa.misc.cn_muligmptype': { + category: 'rsa', + name: 'rsa.misc.cn_muligmptype', + type: 'keyword', + }, + 'rsa.misc.cn_sampalgo': { + category: 'rsa', + name: 'rsa.misc.cn_sampalgo', + type: 'keyword', + }, + 'rsa.misc.cn_sampint': { + category: 'rsa', + name: 'rsa.misc.cn_sampint', + type: 'keyword', + }, + 'rsa.misc.cn_seqctr': { + category: 'rsa', + name: 'rsa.misc.cn_seqctr', + type: 'keyword', + }, + 'rsa.misc.cn_spackets': { + category: 'rsa', + name: 'rsa.misc.cn_spackets', + type: 'keyword', + }, + 'rsa.misc.cn_src_tos': { + category: 'rsa', + name: 'rsa.misc.cn_src_tos', + type: 'keyword', + }, + 'rsa.misc.cn_src_vlan': { + category: 'rsa', + name: 'rsa.misc.cn_src_vlan', + type: 'keyword', + }, + 'rsa.misc.cn_sysuptime': { + category: 'rsa', + name: 'rsa.misc.cn_sysuptime', + type: 'keyword', + }, + 'rsa.misc.cn_template_id': { + category: 'rsa', + name: 'rsa.misc.cn_template_id', + type: 'keyword', + }, + 'rsa.misc.cn_totbytsexp': { + category: 'rsa', + name: 'rsa.misc.cn_totbytsexp', + type: 'keyword', + }, + 'rsa.misc.cn_totflowexp': { + category: 'rsa', + name: 'rsa.misc.cn_totflowexp', + type: 'keyword', + }, + 'rsa.misc.cn_totpcktsexp': { + category: 'rsa', + name: 'rsa.misc.cn_totpcktsexp', + type: 'keyword', + }, + 'rsa.misc.cn_unixnanosecs': { + category: 'rsa', + name: 'rsa.misc.cn_unixnanosecs', + type: 'keyword', + }, + 'rsa.misc.cn_v6flowlabel': { + category: 'rsa', + name: 'rsa.misc.cn_v6flowlabel', + type: 'keyword', + }, + 'rsa.misc.cn_v6optheaders': { + category: 'rsa', + name: 'rsa.misc.cn_v6optheaders', + type: 'keyword', + }, + 'rsa.misc.comp_class': { + category: 'rsa', + name: 'rsa.misc.comp_class', + type: 'keyword', + }, + 'rsa.misc.comp_name': { + category: 'rsa', + name: 'rsa.misc.comp_name', + type: 'keyword', + }, + 'rsa.misc.comp_rbytes': { + category: 'rsa', + name: 'rsa.misc.comp_rbytes', + type: 'keyword', + }, + 'rsa.misc.comp_sbytes': { + category: 'rsa', + name: 'rsa.misc.comp_sbytes', + type: 'keyword', + }, + 'rsa.misc.cpu_data': { + category: 'rsa', + name: 'rsa.misc.cpu_data', + type: 'keyword', + }, + 'rsa.misc.criticality': { + category: 'rsa', + name: 'rsa.misc.criticality', + type: 'keyword', + }, + 'rsa.misc.cs_agency_dst': { + category: 'rsa', + name: 'rsa.misc.cs_agency_dst', + type: 'keyword', + }, + 'rsa.misc.cs_analyzedby': { + category: 'rsa', + name: 'rsa.misc.cs_analyzedby', + type: 'keyword', + }, + 'rsa.misc.cs_av_other': { + category: 'rsa', + name: 'rsa.misc.cs_av_other', + type: 'keyword', + }, + 'rsa.misc.cs_av_primary': { + category: 'rsa', + name: 'rsa.misc.cs_av_primary', + type: 'keyword', + }, + 'rsa.misc.cs_av_secondary': { + category: 'rsa', + name: 'rsa.misc.cs_av_secondary', + type: 'keyword', + }, + 'rsa.misc.cs_bgpv6nxthop': { + category: 'rsa', + name: 'rsa.misc.cs_bgpv6nxthop', + type: 'keyword', + }, + 'rsa.misc.cs_bit9status': { + category: 'rsa', + name: 'rsa.misc.cs_bit9status', + type: 'keyword', + }, + 'rsa.misc.cs_context': { + category: 'rsa', + name: 'rsa.misc.cs_context', + type: 'keyword', + }, + 'rsa.misc.cs_control': { + category: 'rsa', + name: 'rsa.misc.cs_control', + type: 'keyword', + }, + 'rsa.misc.cs_data': { + category: 'rsa', + name: 'rsa.misc.cs_data', + type: 'keyword', + }, + 'rsa.misc.cs_datecret': { + category: 'rsa', + name: 'rsa.misc.cs_datecret', + type: 'keyword', + }, + 'rsa.misc.cs_dst_tld': { + category: 'rsa', + name: 'rsa.misc.cs_dst_tld', + type: 'keyword', + }, + 'rsa.misc.cs_eth_dst_ven': { + category: 'rsa', + name: 'rsa.misc.cs_eth_dst_ven', + type: 'keyword', + }, + 'rsa.misc.cs_eth_src_ven': { + category: 'rsa', + name: 'rsa.misc.cs_eth_src_ven', + type: 'keyword', + }, + 'rsa.misc.cs_event_uuid': { + category: 'rsa', + name: 'rsa.misc.cs_event_uuid', + type: 'keyword', + }, + 'rsa.misc.cs_filetype': { + category: 'rsa', + name: 'rsa.misc.cs_filetype', + type: 'keyword', + }, + 'rsa.misc.cs_fld': { + category: 'rsa', + name: 'rsa.misc.cs_fld', + type: 'keyword', + }, + 'rsa.misc.cs_if_desc': { + category: 'rsa', + name: 'rsa.misc.cs_if_desc', + type: 'keyword', + }, + 'rsa.misc.cs_if_name': { + category: 'rsa', + name: 'rsa.misc.cs_if_name', + type: 'keyword', + }, + 'rsa.misc.cs_ip_next_hop': { + category: 'rsa', + name: 'rsa.misc.cs_ip_next_hop', + type: 'keyword', + }, + 'rsa.misc.cs_ipv4dstpre': { + category: 'rsa', + name: 'rsa.misc.cs_ipv4dstpre', + type: 'keyword', + }, + 'rsa.misc.cs_ipv4srcpre': { + category: 'rsa', + name: 'rsa.misc.cs_ipv4srcpre', + type: 'keyword', + }, + 'rsa.misc.cs_lifetime': { + category: 'rsa', + name: 'rsa.misc.cs_lifetime', + type: 'keyword', + }, + 'rsa.misc.cs_log_medium': { + category: 'rsa', + name: 'rsa.misc.cs_log_medium', + type: 'keyword', + }, + 'rsa.misc.cs_loginname': { + category: 'rsa', + name: 'rsa.misc.cs_loginname', + type: 'keyword', + }, + 'rsa.misc.cs_modulescore': { + category: 'rsa', + name: 'rsa.misc.cs_modulescore', + type: 'keyword', + }, + 'rsa.misc.cs_modulesign': { + category: 'rsa', + name: 'rsa.misc.cs_modulesign', + type: 'keyword', + }, + 'rsa.misc.cs_opswatresult': { + category: 'rsa', + name: 'rsa.misc.cs_opswatresult', + type: 'keyword', + }, + 'rsa.misc.cs_payload': { + category: 'rsa', + name: 'rsa.misc.cs_payload', + type: 'keyword', + }, + 'rsa.misc.cs_registrant': { + category: 'rsa', + name: 'rsa.misc.cs_registrant', + type: 'keyword', + }, + 'rsa.misc.cs_registrar': { + category: 'rsa', + name: 'rsa.misc.cs_registrar', + type: 'keyword', + }, + 'rsa.misc.cs_represult': { + category: 'rsa', + name: 'rsa.misc.cs_represult', + type: 'keyword', + }, + 'rsa.misc.cs_rpayload': { + category: 'rsa', + name: 'rsa.misc.cs_rpayload', + type: 'keyword', + }, + 'rsa.misc.cs_sampler_name': { + category: 'rsa', + name: 'rsa.misc.cs_sampler_name', + type: 'keyword', + }, + 'rsa.misc.cs_sourcemodule': { + category: 'rsa', + name: 'rsa.misc.cs_sourcemodule', + type: 'keyword', + }, + 'rsa.misc.cs_streams': { + category: 'rsa', + name: 'rsa.misc.cs_streams', + type: 'keyword', + }, + 'rsa.misc.cs_targetmodule': { + category: 'rsa', + name: 'rsa.misc.cs_targetmodule', + type: 'keyword', + }, + 'rsa.misc.cs_v6nxthop': { + category: 'rsa', + name: 'rsa.misc.cs_v6nxthop', + type: 'keyword', + }, + 'rsa.misc.cs_whois_server': { + category: 'rsa', + name: 'rsa.misc.cs_whois_server', + type: 'keyword', + }, + 'rsa.misc.cs_yararesult': { + category: 'rsa', + name: 'rsa.misc.cs_yararesult', + type: 'keyword', + }, + 'rsa.misc.description': { + category: 'rsa', + name: 'rsa.misc.description', + type: 'keyword', + }, + 'rsa.misc.devvendor': { + category: 'rsa', + name: 'rsa.misc.devvendor', + type: 'keyword', + }, + 'rsa.misc.distance': { + category: 'rsa', + name: 'rsa.misc.distance', + type: 'keyword', + }, + 'rsa.misc.dstburb': { + category: 'rsa', + name: 'rsa.misc.dstburb', + type: 'keyword', + }, + 'rsa.misc.edomain': { + category: 'rsa', + name: 'rsa.misc.edomain', + type: 'keyword', + }, + 'rsa.misc.edomaub': { + category: 'rsa', + name: 'rsa.misc.edomaub', + type: 'keyword', + }, + 'rsa.misc.euid': { + category: 'rsa', + name: 'rsa.misc.euid', + type: 'keyword', + }, + 'rsa.misc.facility': { + category: 'rsa', + name: 'rsa.misc.facility', + type: 'keyword', + }, + 'rsa.misc.finterface': { + category: 'rsa', + name: 'rsa.misc.finterface', + type: 'keyword', + }, + 'rsa.misc.flags': { + category: 'rsa', + name: 'rsa.misc.flags', + type: 'keyword', + }, + 'rsa.misc.gaddr': { + category: 'rsa', + name: 'rsa.misc.gaddr', + type: 'keyword', + }, + 'rsa.misc.id3': { + category: 'rsa', + name: 'rsa.misc.id3', + type: 'keyword', + }, + 'rsa.misc.im_buddyname': { + category: 'rsa', + name: 'rsa.misc.im_buddyname', + type: 'keyword', + }, + 'rsa.misc.im_croomid': { + category: 'rsa', + name: 'rsa.misc.im_croomid', + type: 'keyword', + }, + 'rsa.misc.im_croomtype': { + category: 'rsa', + name: 'rsa.misc.im_croomtype', + type: 'keyword', + }, + 'rsa.misc.im_members': { + category: 'rsa', + name: 'rsa.misc.im_members', + type: 'keyword', + }, + 'rsa.misc.im_username': { + category: 'rsa', + name: 'rsa.misc.im_username', + type: 'keyword', + }, + 'rsa.misc.ipkt': { + category: 'rsa', + name: 'rsa.misc.ipkt', + type: 'keyword', + }, + 'rsa.misc.ipscat': { + category: 'rsa', + name: 'rsa.misc.ipscat', + type: 'keyword', + }, + 'rsa.misc.ipspri': { + category: 'rsa', + name: 'rsa.misc.ipspri', + type: 'keyword', + }, + 'rsa.misc.latitude': { + category: 'rsa', + name: 'rsa.misc.latitude', + type: 'keyword', + }, + 'rsa.misc.linenum': { + category: 'rsa', + name: 'rsa.misc.linenum', + type: 'keyword', + }, + 'rsa.misc.list_name': { + category: 'rsa', + name: 'rsa.misc.list_name', + type: 'keyword', + }, + 'rsa.misc.load_data': { + category: 'rsa', + name: 'rsa.misc.load_data', + type: 'keyword', + }, + 'rsa.misc.location_floor': { + category: 'rsa', + name: 'rsa.misc.location_floor', + type: 'keyword', + }, + 'rsa.misc.location_mark': { + category: 'rsa', + name: 'rsa.misc.location_mark', + type: 'keyword', + }, + 'rsa.misc.log_id': { + category: 'rsa', + name: 'rsa.misc.log_id', + type: 'keyword', + }, + 'rsa.misc.log_type': { + category: 'rsa', + name: 'rsa.misc.log_type', + type: 'keyword', + }, + 'rsa.misc.logid': { + category: 'rsa', + name: 'rsa.misc.logid', + type: 'keyword', + }, + 'rsa.misc.logip': { + category: 'rsa', + name: 'rsa.misc.logip', + type: 'keyword', + }, + 'rsa.misc.logname': { + category: 'rsa', + name: 'rsa.misc.logname', + type: 'keyword', + }, + 'rsa.misc.longitude': { + category: 'rsa', + name: 'rsa.misc.longitude', + type: 'keyword', + }, + 'rsa.misc.lport': { + category: 'rsa', + name: 'rsa.misc.lport', + type: 'keyword', + }, + 'rsa.misc.mbug_data': { + category: 'rsa', + name: 'rsa.misc.mbug_data', + type: 'keyword', + }, + 'rsa.misc.misc_name': { + category: 'rsa', + name: 'rsa.misc.misc_name', + type: 'keyword', + }, + 'rsa.misc.msg_type': { + category: 'rsa', + name: 'rsa.misc.msg_type', + type: 'keyword', + }, + 'rsa.misc.msgid': { + category: 'rsa', + name: 'rsa.misc.msgid', + type: 'keyword', + }, + 'rsa.misc.netsessid': { + category: 'rsa', + name: 'rsa.misc.netsessid', + type: 'keyword', + }, + 'rsa.misc.num': { + category: 'rsa', + name: 'rsa.misc.num', + type: 'keyword', + }, + 'rsa.misc.number1': { + category: 'rsa', + name: 'rsa.misc.number1', + type: 'keyword', + }, + 'rsa.misc.number2': { + category: 'rsa', + name: 'rsa.misc.number2', + type: 'keyword', + }, + 'rsa.misc.nwwn': { + category: 'rsa', + name: 'rsa.misc.nwwn', + type: 'keyword', + }, + 'rsa.misc.object': { + category: 'rsa', + name: 'rsa.misc.object', + type: 'keyword', + }, + 'rsa.misc.operation': { + category: 'rsa', + name: 'rsa.misc.operation', + type: 'keyword', + }, + 'rsa.misc.opkt': { + category: 'rsa', + name: 'rsa.misc.opkt', + type: 'keyword', + }, + 'rsa.misc.orig_from': { + category: 'rsa', + name: 'rsa.misc.orig_from', + type: 'keyword', + }, + 'rsa.misc.owner_id': { + category: 'rsa', + name: 'rsa.misc.owner_id', + type: 'keyword', + }, + 'rsa.misc.p_action': { + category: 'rsa', + name: 'rsa.misc.p_action', + type: 'keyword', + }, + 'rsa.misc.p_filter': { + category: 'rsa', + name: 'rsa.misc.p_filter', + type: 'keyword', + }, + 'rsa.misc.p_group_object': { + category: 'rsa', + name: 'rsa.misc.p_group_object', + type: 'keyword', + }, + 'rsa.misc.p_id': { + category: 'rsa', + name: 'rsa.misc.p_id', + type: 'keyword', + }, + 'rsa.misc.p_msgid1': { + category: 'rsa', + name: 'rsa.misc.p_msgid1', + type: 'keyword', + }, + 'rsa.misc.p_msgid2': { + category: 'rsa', + name: 'rsa.misc.p_msgid2', + type: 'keyword', + }, + 'rsa.misc.p_result1': { + category: 'rsa', + name: 'rsa.misc.p_result1', + type: 'keyword', + }, + 'rsa.misc.password_chg': { + category: 'rsa', + name: 'rsa.misc.password_chg', + type: 'keyword', + }, + 'rsa.misc.password_expire': { + category: 'rsa', + name: 'rsa.misc.password_expire', + type: 'keyword', + }, + 'rsa.misc.permgranted': { + category: 'rsa', + name: 'rsa.misc.permgranted', + type: 'keyword', + }, + 'rsa.misc.permwanted': { + category: 'rsa', + name: 'rsa.misc.permwanted', + type: 'keyword', + }, + 'rsa.misc.pgid': { + category: 'rsa', + name: 'rsa.misc.pgid', + type: 'keyword', + }, + 'rsa.misc.policyUUID': { + category: 'rsa', + name: 'rsa.misc.policyUUID', + type: 'keyword', + }, + 'rsa.misc.prog_asp_num': { + category: 'rsa', + name: 'rsa.misc.prog_asp_num', + type: 'keyword', + }, + 'rsa.misc.program': { + category: 'rsa', + name: 'rsa.misc.program', + type: 'keyword', + }, + 'rsa.misc.real_data': { + category: 'rsa', + name: 'rsa.misc.real_data', + type: 'keyword', + }, + 'rsa.misc.rec_asp_device': { + category: 'rsa', + name: 'rsa.misc.rec_asp_device', + type: 'keyword', + }, + 'rsa.misc.rec_asp_num': { + category: 'rsa', + name: 'rsa.misc.rec_asp_num', + type: 'keyword', + }, + 'rsa.misc.rec_library': { + category: 'rsa', + name: 'rsa.misc.rec_library', + type: 'keyword', + }, + 'rsa.misc.recordnum': { + category: 'rsa', + name: 'rsa.misc.recordnum', + type: 'keyword', + }, + 'rsa.misc.ruid': { + category: 'rsa', + name: 'rsa.misc.ruid', + type: 'keyword', + }, + 'rsa.misc.sburb': { + category: 'rsa', + name: 'rsa.misc.sburb', + type: 'keyword', + }, + 'rsa.misc.sdomain_fld': { + category: 'rsa', + name: 'rsa.misc.sdomain_fld', + type: 'keyword', + }, + 'rsa.misc.sec': { + category: 'rsa', + name: 'rsa.misc.sec', + type: 'keyword', + }, + 'rsa.misc.sensorname': { + category: 'rsa', + name: 'rsa.misc.sensorname', + type: 'keyword', + }, + 'rsa.misc.seqnum': { + category: 'rsa', + name: 'rsa.misc.seqnum', + type: 'keyword', + }, + 'rsa.misc.session': { + category: 'rsa', + name: 'rsa.misc.session', + type: 'keyword', + }, + 'rsa.misc.sessiontype': { + category: 'rsa', + name: 'rsa.misc.sessiontype', + type: 'keyword', + }, + 'rsa.misc.sigUUID': { + category: 'rsa', + name: 'rsa.misc.sigUUID', + type: 'keyword', + }, + 'rsa.misc.spi': { + category: 'rsa', + name: 'rsa.misc.spi', + type: 'keyword', + }, + 'rsa.misc.srcburb': { + category: 'rsa', + name: 'rsa.misc.srcburb', + type: 'keyword', + }, + 'rsa.misc.srcdom': { + category: 'rsa', + name: 'rsa.misc.srcdom', + type: 'keyword', + }, + 'rsa.misc.srcservice': { + category: 'rsa', + name: 'rsa.misc.srcservice', + type: 'keyword', + }, + 'rsa.misc.state': { + category: 'rsa', + name: 'rsa.misc.state', + type: 'keyword', + }, + 'rsa.misc.status1': { + category: 'rsa', + name: 'rsa.misc.status1', + type: 'keyword', + }, + 'rsa.misc.svcno': { + category: 'rsa', + name: 'rsa.misc.svcno', + type: 'keyword', + }, + 'rsa.misc.system': { + category: 'rsa', + name: 'rsa.misc.system', + type: 'keyword', + }, + 'rsa.misc.tbdstr1': { + category: 'rsa', + name: 'rsa.misc.tbdstr1', + type: 'keyword', + }, + 'rsa.misc.tgtdom': { + category: 'rsa', + name: 'rsa.misc.tgtdom', + type: 'keyword', + }, + 'rsa.misc.tgtdomain': { + category: 'rsa', + name: 'rsa.misc.tgtdomain', + type: 'keyword', + }, + 'rsa.misc.threshold': { + category: 'rsa', + name: 'rsa.misc.threshold', + type: 'keyword', + }, + 'rsa.misc.type1': { + category: 'rsa', + name: 'rsa.misc.type1', + type: 'keyword', + }, + 'rsa.misc.udb_class': { + category: 'rsa', + name: 'rsa.misc.udb_class', + type: 'keyword', + }, + 'rsa.misc.url_fld': { + category: 'rsa', + name: 'rsa.misc.url_fld', + type: 'keyword', + }, + 'rsa.misc.user_div': { + category: 'rsa', + name: 'rsa.misc.user_div', + type: 'keyword', + }, + 'rsa.misc.userid': { + category: 'rsa', + name: 'rsa.misc.userid', + type: 'keyword', + }, + 'rsa.misc.username_fld': { + category: 'rsa', + name: 'rsa.misc.username_fld', + type: 'keyword', + }, + 'rsa.misc.utcstamp': { + category: 'rsa', + name: 'rsa.misc.utcstamp', + type: 'keyword', + }, + 'rsa.misc.v_instafname': { + category: 'rsa', + name: 'rsa.misc.v_instafname', + type: 'keyword', + }, + 'rsa.misc.virt_data': { + category: 'rsa', + name: 'rsa.misc.virt_data', + type: 'keyword', + }, + 'rsa.misc.vpnid': { + category: 'rsa', + name: 'rsa.misc.vpnid', + type: 'keyword', + }, + 'rsa.misc.autorun_type': { + category: 'rsa', + description: 'This is used to capture Auto Run type', + name: 'rsa.misc.autorun_type', + type: 'keyword', + }, + 'rsa.misc.cc_number': { + category: 'rsa', + description: 'Valid Credit Card Numbers only', + name: 'rsa.misc.cc_number', + type: 'long', + }, + 'rsa.misc.content': { + category: 'rsa', + description: 'This key captures the content type from protocol headers', + name: 'rsa.misc.content', + type: 'keyword', + }, + 'rsa.misc.ein_number': { + category: 'rsa', + description: 'Employee Identification Numbers only', + name: 'rsa.misc.ein_number', + type: 'long', + }, + 'rsa.misc.found': { + category: 'rsa', + description: 'This is used to capture the results of regex match', + name: 'rsa.misc.found', + type: 'keyword', + }, + 'rsa.misc.language': { + category: 'rsa', + description: 'This is used to capture list of languages the client support and what it prefers', + name: 'rsa.misc.language', + type: 'keyword', + }, + 'rsa.misc.lifetime': { + category: 'rsa', + description: 'This key is used to capture the session lifetime in seconds.', + name: 'rsa.misc.lifetime', + type: 'long', + }, + 'rsa.misc.link': { + category: 'rsa', + description: + 'This key is used to link the sessions together. This key should never be used to parse Meta data from a session (Logs/Packets) Directly, this is a Reserved key in NetWitness', + name: 'rsa.misc.link', + type: 'keyword', + }, + 'rsa.misc.match': { + category: 'rsa', + description: 'This key is for regex match name from search.ini', + name: 'rsa.misc.match', + type: 'keyword', + }, + 'rsa.misc.param_dst': { + category: 'rsa', + description: 'This key captures the command line/launch argument of the target process or file', + name: 'rsa.misc.param_dst', + type: 'keyword', + }, + 'rsa.misc.param_src': { + category: 'rsa', + description: 'This key captures source parameter', + name: 'rsa.misc.param_src', + type: 'keyword', + }, + 'rsa.misc.search_text': { + category: 'rsa', + description: 'This key captures the Search Text used', + name: 'rsa.misc.search_text', + type: 'keyword', + }, + 'rsa.misc.sig_name': { + category: 'rsa', + description: 'This key is used to capture the Signature Name only.', + name: 'rsa.misc.sig_name', + type: 'keyword', + }, + 'rsa.misc.snmp_value': { + category: 'rsa', + description: 'SNMP set request value', + name: 'rsa.misc.snmp_value', + type: 'keyword', + }, + 'rsa.misc.streams': { + category: 'rsa', + description: 'This key captures number of streams in session', + name: 'rsa.misc.streams', + type: 'long', + }, + 'rsa.db.index': { + category: 'rsa', + description: 'This key captures IndexID of the index.', + name: 'rsa.db.index', + type: 'keyword', + }, + 'rsa.db.instance': { + category: 'rsa', + description: 'This key is used to capture the database server instance name', + name: 'rsa.db.instance', + type: 'keyword', + }, + 'rsa.db.database': { + category: 'rsa', + description: + 'This key is used to capture the name of a database or an instance as seen in a session', + name: 'rsa.db.database', + type: 'keyword', + }, + 'rsa.db.transact_id': { + category: 'rsa', + description: 'This key captures the SQL transantion ID of the current session', + name: 'rsa.db.transact_id', + type: 'keyword', + }, + 'rsa.db.permissions': { + category: 'rsa', + description: 'This key captures permission or privilege level assigned to a resource.', + name: 'rsa.db.permissions', + type: 'keyword', + }, + 'rsa.db.table_name': { + category: 'rsa', + description: 'This key is used to capture the table name', + name: 'rsa.db.table_name', + type: 'keyword', + }, + 'rsa.db.db_id': { + category: 'rsa', + description: 'This key is used to capture the unique identifier for a database', + name: 'rsa.db.db_id', + type: 'keyword', + }, + 'rsa.db.db_pid': { + category: 'rsa', + description: 'This key captures the process id of a connection with database server', + name: 'rsa.db.db_pid', + type: 'long', + }, + 'rsa.db.lread': { + category: 'rsa', + description: 'This key is used for the number of logical reads', + name: 'rsa.db.lread', + type: 'long', + }, + 'rsa.db.lwrite': { + category: 'rsa', + description: 'This key is used for the number of logical writes', + name: 'rsa.db.lwrite', + type: 'long', + }, + 'rsa.db.pread': { + category: 'rsa', + description: 'This key is used for the number of physical writes', + name: 'rsa.db.pread', + type: 'long', + }, + 'rsa.network.alias_host': { + category: 'rsa', + description: + 'This key should be used when the source or destination context of a hostname is not clear.Also it captures the Device Hostname. Any Hostname that isnt ad.computer.', + name: 'rsa.network.alias_host', + type: 'keyword', + }, + 'rsa.network.domain': { + category: 'rsa', + name: 'rsa.network.domain', + type: 'keyword', + }, + 'rsa.network.host_dst': { + category: 'rsa', + description: 'This key should only be used when it’s a Destination Hostname', + name: 'rsa.network.host_dst', + type: 'keyword', + }, + 'rsa.network.network_service': { + category: 'rsa', + description: 'This is used to capture layer 7 protocols/service names', + name: 'rsa.network.network_service', + type: 'keyword', + }, + 'rsa.network.interface': { + category: 'rsa', + description: + 'This key should be used when the source or destination context of an interface is not clear', + name: 'rsa.network.interface', + type: 'keyword', + }, + 'rsa.network.network_port': { + category: 'rsa', + description: + 'Deprecated, use port. NOTE: There is a type discrepancy as currently used, TM: Int32, INDEX: UInt64 (why neither chose the correct UInt16?!)', + name: 'rsa.network.network_port', + type: 'long', + }, + 'rsa.network.eth_host': { + category: 'rsa', + description: 'Deprecated, use alias.mac', + name: 'rsa.network.eth_host', + type: 'keyword', + }, + 'rsa.network.sinterface': { + category: 'rsa', + description: 'This key should only be used when it’s a Source Interface', + name: 'rsa.network.sinterface', + type: 'keyword', + }, + 'rsa.network.dinterface': { + category: 'rsa', + description: 'This key should only be used when it’s a Destination Interface', + name: 'rsa.network.dinterface', + type: 'keyword', + }, + 'rsa.network.vlan': { + category: 'rsa', + description: 'This key should only be used to capture the ID of the Virtual LAN', + name: 'rsa.network.vlan', + type: 'long', + }, + 'rsa.network.zone_src': { + category: 'rsa', + description: 'This key should only be used when it’s a Source Zone.', + name: 'rsa.network.zone_src', + type: 'keyword', + }, + 'rsa.network.zone': { + category: 'rsa', + description: + 'This key should be used when the source or destination context of a Zone is not clear', + name: 'rsa.network.zone', + type: 'keyword', + }, + 'rsa.network.zone_dst': { + category: 'rsa', + description: 'This key should only be used when it’s a Destination Zone.', + name: 'rsa.network.zone_dst', + type: 'keyword', + }, + 'rsa.network.gateway': { + category: 'rsa', + description: 'This key is used to capture the IP Address of the gateway', + name: 'rsa.network.gateway', + type: 'keyword', + }, + 'rsa.network.icmp_type': { + category: 'rsa', + description: 'This key is used to capture the ICMP type only', + name: 'rsa.network.icmp_type', + type: 'long', + }, + 'rsa.network.mask': { + category: 'rsa', + description: 'This key is used to capture the device network IPmask.', + name: 'rsa.network.mask', + type: 'keyword', + }, + 'rsa.network.icmp_code': { + category: 'rsa', + description: 'This key is used to capture the ICMP code only', + name: 'rsa.network.icmp_code', + type: 'long', + }, + 'rsa.network.protocol_detail': { + category: 'rsa', + description: 'This key should be used to capture additional protocol information', + name: 'rsa.network.protocol_detail', + type: 'keyword', + }, + 'rsa.network.dmask': { + category: 'rsa', + description: 'This key is used for Destionation Device network mask', + name: 'rsa.network.dmask', + type: 'keyword', + }, + 'rsa.network.port': { + category: 'rsa', + description: + 'This key should only be used to capture a Network Port when the directionality is not clear', + name: 'rsa.network.port', + type: 'long', + }, + 'rsa.network.smask': { + category: 'rsa', + description: 'This key is used for capturing source Network Mask', + name: 'rsa.network.smask', + type: 'keyword', + }, + 'rsa.network.netname': { + category: 'rsa', + description: + 'This key is used to capture the network name associated with an IP range. This is configured by the end user.', + name: 'rsa.network.netname', + type: 'keyword', + }, + 'rsa.network.paddr': { + category: 'rsa', + description: 'Deprecated', + name: 'rsa.network.paddr', + type: 'ip', + }, + 'rsa.network.faddr': { + category: 'rsa', + name: 'rsa.network.faddr', + type: 'keyword', + }, + 'rsa.network.lhost': { + category: 'rsa', + name: 'rsa.network.lhost', + type: 'keyword', + }, + 'rsa.network.origin': { + category: 'rsa', + name: 'rsa.network.origin', + type: 'keyword', + }, + 'rsa.network.remote_domain_id': { + category: 'rsa', + name: 'rsa.network.remote_domain_id', + type: 'keyword', + }, + 'rsa.network.addr': { + category: 'rsa', + name: 'rsa.network.addr', + type: 'keyword', + }, + 'rsa.network.dns_a_record': { + category: 'rsa', + name: 'rsa.network.dns_a_record', + type: 'keyword', + }, + 'rsa.network.dns_ptr_record': { + category: 'rsa', + name: 'rsa.network.dns_ptr_record', + type: 'keyword', + }, + 'rsa.network.fhost': { + category: 'rsa', + name: 'rsa.network.fhost', + type: 'keyword', + }, + 'rsa.network.fport': { + category: 'rsa', + name: 'rsa.network.fport', + type: 'keyword', + }, + 'rsa.network.laddr': { + category: 'rsa', + name: 'rsa.network.laddr', + type: 'keyword', + }, + 'rsa.network.linterface': { + category: 'rsa', + name: 'rsa.network.linterface', + type: 'keyword', + }, + 'rsa.network.phost': { + category: 'rsa', + name: 'rsa.network.phost', + type: 'keyword', + }, + 'rsa.network.ad_computer_dst': { + category: 'rsa', + description: 'Deprecated, use host.dst', + name: 'rsa.network.ad_computer_dst', + type: 'keyword', + }, + 'rsa.network.eth_type': { + category: 'rsa', + description: 'This key is used to capture Ethernet Type, Used for Layer 3 Protocols Only', + name: 'rsa.network.eth_type', + type: 'long', + }, + 'rsa.network.ip_proto': { + category: 'rsa', + description: + 'This key should be used to capture the Protocol number, all the protocol nubers are converted into string in UI', + name: 'rsa.network.ip_proto', + type: 'long', + }, + 'rsa.network.dns_cname_record': { + category: 'rsa', + name: 'rsa.network.dns_cname_record', + type: 'keyword', + }, + 'rsa.network.dns_id': { + category: 'rsa', + name: 'rsa.network.dns_id', + type: 'keyword', + }, + 'rsa.network.dns_opcode': { + category: 'rsa', + name: 'rsa.network.dns_opcode', + type: 'keyword', + }, + 'rsa.network.dns_resp': { + category: 'rsa', + name: 'rsa.network.dns_resp', + type: 'keyword', + }, + 'rsa.network.dns_type': { + category: 'rsa', + name: 'rsa.network.dns_type', + type: 'keyword', + }, + 'rsa.network.domain1': { + category: 'rsa', + name: 'rsa.network.domain1', + type: 'keyword', + }, + 'rsa.network.host_type': { + category: 'rsa', + name: 'rsa.network.host_type', + type: 'keyword', + }, + 'rsa.network.packet_length': { + category: 'rsa', + name: 'rsa.network.packet_length', + type: 'keyword', + }, + 'rsa.network.host_orig': { + category: 'rsa', + description: + 'This is used to capture the original hostname in case of a Forwarding Agent or a Proxy in between.', + name: 'rsa.network.host_orig', + type: 'keyword', + }, + 'rsa.network.rpayload': { + category: 'rsa', + description: + 'This key is used to capture the total number of payload bytes seen in the retransmitted packets.', + name: 'rsa.network.rpayload', + type: 'keyword', + }, + 'rsa.network.vlan_name': { + category: 'rsa', + description: 'This key should only be used to capture the name of the Virtual LAN', + name: 'rsa.network.vlan_name', + type: 'keyword', + }, + 'rsa.investigations.ec_activity': { + category: 'rsa', + description: 'This key captures the particular event activity(Ex:Logoff)', + name: 'rsa.investigations.ec_activity', + type: 'keyword', + }, + 'rsa.investigations.ec_theme': { + category: 'rsa', + description: 'This key captures the Theme of a particular Event(Ex:Authentication)', + name: 'rsa.investigations.ec_theme', + type: 'keyword', + }, + 'rsa.investigations.ec_subject': { + category: 'rsa', + description: 'This key captures the Subject of a particular Event(Ex:User)', + name: 'rsa.investigations.ec_subject', + type: 'keyword', + }, + 'rsa.investigations.ec_outcome': { + category: 'rsa', + description: 'This key captures the outcome of a particular Event(Ex:Success)', + name: 'rsa.investigations.ec_outcome', + type: 'keyword', + }, + 'rsa.investigations.event_cat': { + category: 'rsa', + description: 'This key captures the Event category number', + name: 'rsa.investigations.event_cat', + type: 'long', + }, + 'rsa.investigations.event_cat_name': { + category: 'rsa', + description: 'This key captures the event category name corresponding to the event cat code', + name: 'rsa.investigations.event_cat_name', + type: 'keyword', + }, + 'rsa.investigations.event_vcat': { + category: 'rsa', + description: + 'This is a vendor supplied category. This should be used in situations where the vendor has adopted their own event_category taxonomy.', + name: 'rsa.investigations.event_vcat', + type: 'keyword', + }, + 'rsa.investigations.analysis_file': { + category: 'rsa', + description: + 'This is used to capture all indicators used in a File Analysis. This key should be used to capture an analysis of a file', + name: 'rsa.investigations.analysis_file', + type: 'keyword', + }, + 'rsa.investigations.analysis_service': { + category: 'rsa', + description: + 'This is used to capture all indicators used in a Service Analysis. This key should be used to capture an analysis of a service', + name: 'rsa.investigations.analysis_service', + type: 'keyword', + }, + 'rsa.investigations.analysis_session': { + category: 'rsa', + description: + 'This is used to capture all indicators used for a Session Analysis. This key should be used to capture an analysis of a session', + name: 'rsa.investigations.analysis_session', + type: 'keyword', + }, + 'rsa.investigations.boc': { + category: 'rsa', + description: 'This is used to capture behaviour of compromise', + name: 'rsa.investigations.boc', + type: 'keyword', + }, + 'rsa.investigations.eoc': { + category: 'rsa', + description: 'This is used to capture Enablers of Compromise', + name: 'rsa.investigations.eoc', + type: 'keyword', + }, + 'rsa.investigations.inv_category': { + category: 'rsa', + description: 'This used to capture investigation category', + name: 'rsa.investigations.inv_category', + type: 'keyword', + }, + 'rsa.investigations.inv_context': { + category: 'rsa', + description: 'This used to capture investigation context', + name: 'rsa.investigations.inv_context', + type: 'keyword', + }, + 'rsa.investigations.ioc': { + category: 'rsa', + description: 'This is key capture indicator of compromise', + name: 'rsa.investigations.ioc', + type: 'keyword', + }, + 'rsa.counters.dclass_c1': { + category: 'rsa', + description: + 'This is a generic counter key that should be used with the label dclass.c1.str only', + name: 'rsa.counters.dclass_c1', + type: 'long', + }, + 'rsa.counters.dclass_c2': { + category: 'rsa', + description: + 'This is a generic counter key that should be used with the label dclass.c2.str only', + name: 'rsa.counters.dclass_c2', + type: 'long', + }, + 'rsa.counters.event_counter': { + category: 'rsa', + description: 'This is used to capture the number of times an event repeated', + name: 'rsa.counters.event_counter', + type: 'long', + }, + 'rsa.counters.dclass_r1': { + category: 'rsa', + description: + 'This is a generic ratio key that should be used with the label dclass.r1.str only', + name: 'rsa.counters.dclass_r1', + type: 'keyword', + }, + 'rsa.counters.dclass_c3': { + category: 'rsa', + description: + 'This is a generic counter key that should be used with the label dclass.c3.str only', + name: 'rsa.counters.dclass_c3', + type: 'long', + }, + 'rsa.counters.dclass_c1_str': { + category: 'rsa', + description: + 'This is a generic counter string key that should be used with the label dclass.c1 only', + name: 'rsa.counters.dclass_c1_str', + type: 'keyword', + }, + 'rsa.counters.dclass_c2_str': { + category: 'rsa', + description: + 'This is a generic counter string key that should be used with the label dclass.c2 only', + name: 'rsa.counters.dclass_c2_str', + type: 'keyword', + }, + 'rsa.counters.dclass_r1_str': { + category: 'rsa', + description: + 'This is a generic ratio string key that should be used with the label dclass.r1 only', + name: 'rsa.counters.dclass_r1_str', + type: 'keyword', + }, + 'rsa.counters.dclass_r2': { + category: 'rsa', + description: + 'This is a generic ratio key that should be used with the label dclass.r2.str only', + name: 'rsa.counters.dclass_r2', + type: 'keyword', + }, + 'rsa.counters.dclass_c3_str': { + category: 'rsa', + description: + 'This is a generic counter string key that should be used with the label dclass.c3 only', + name: 'rsa.counters.dclass_c3_str', + type: 'keyword', + }, + 'rsa.counters.dclass_r3': { + category: 'rsa', + description: + 'This is a generic ratio key that should be used with the label dclass.r3.str only', + name: 'rsa.counters.dclass_r3', + type: 'keyword', + }, + 'rsa.counters.dclass_r2_str': { + category: 'rsa', + description: + 'This is a generic ratio string key that should be used with the label dclass.r2 only', + name: 'rsa.counters.dclass_r2_str', + type: 'keyword', + }, + 'rsa.counters.dclass_r3_str': { + category: 'rsa', + description: + 'This is a generic ratio string key that should be used with the label dclass.r3 only', + name: 'rsa.counters.dclass_r3_str', + type: 'keyword', + }, + 'rsa.identity.auth_method': { + category: 'rsa', + description: 'This key is used to capture authentication methods used only', + name: 'rsa.identity.auth_method', + type: 'keyword', + }, + 'rsa.identity.user_role': { + category: 'rsa', + description: 'This key is used to capture the Role of a user only', + name: 'rsa.identity.user_role', + type: 'keyword', + }, + 'rsa.identity.dn': { + category: 'rsa', + description: 'X.500 (LDAP) Distinguished Name', + name: 'rsa.identity.dn', + type: 'keyword', + }, + 'rsa.identity.logon_type': { + category: 'rsa', + description: 'This key is used to capture the type of logon method used.', + name: 'rsa.identity.logon_type', + type: 'keyword', + }, + 'rsa.identity.profile': { + category: 'rsa', + description: 'This key is used to capture the user profile', + name: 'rsa.identity.profile', + type: 'keyword', + }, + 'rsa.identity.accesses': { + category: 'rsa', + description: 'This key is used to capture actual privileges used in accessing an object', + name: 'rsa.identity.accesses', + type: 'keyword', + }, + 'rsa.identity.realm': { + category: 'rsa', + description: 'Radius realm or similar grouping of accounts', + name: 'rsa.identity.realm', + type: 'keyword', + }, + 'rsa.identity.user_sid_dst': { + category: 'rsa', + description: 'This key captures Destination User Session ID', + name: 'rsa.identity.user_sid_dst', + type: 'keyword', + }, + 'rsa.identity.dn_src': { + category: 'rsa', + description: + 'An X.500 (LDAP) Distinguished name that is used in a context that indicates a Source dn', + name: 'rsa.identity.dn_src', + type: 'keyword', + }, + 'rsa.identity.org': { + category: 'rsa', + description: 'This key captures the User organization', + name: 'rsa.identity.org', + type: 'keyword', + }, + 'rsa.identity.dn_dst': { + category: 'rsa', + description: + 'An X.500 (LDAP) Distinguished name that used in a context that indicates a Destination dn', + name: 'rsa.identity.dn_dst', + type: 'keyword', + }, + 'rsa.identity.firstname': { + category: 'rsa', + description: + 'This key is for First Names only, this is used for Healthcare predominantly to capture Patients information', + name: 'rsa.identity.firstname', + type: 'keyword', + }, + 'rsa.identity.lastname': { + category: 'rsa', + description: + 'This key is for Last Names only, this is used for Healthcare predominantly to capture Patients information', + name: 'rsa.identity.lastname', + type: 'keyword', + }, + 'rsa.identity.user_dept': { + category: 'rsa', + description: "User's Department Names only", + name: 'rsa.identity.user_dept', + type: 'keyword', + }, + 'rsa.identity.user_sid_src': { + category: 'rsa', + description: 'This key captures Source User Session ID', + name: 'rsa.identity.user_sid_src', + type: 'keyword', + }, + 'rsa.identity.federated_sp': { + category: 'rsa', + description: + 'This key is the Federated Service Provider. This is the application requesting authentication.', + name: 'rsa.identity.federated_sp', + type: 'keyword', + }, + 'rsa.identity.federated_idp': { + category: 'rsa', + description: + 'This key is the federated Identity Provider. This is the server providing the authentication.', + name: 'rsa.identity.federated_idp', + type: 'keyword', + }, + 'rsa.identity.logon_type_desc': { + category: 'rsa', + description: + "This key is used to capture the textual description of an integer logon type as stored in the meta key 'logon.type'.", + name: 'rsa.identity.logon_type_desc', + type: 'keyword', + }, + 'rsa.identity.middlename': { + category: 'rsa', + description: + 'This key is for Middle Names only, this is used for Healthcare predominantly to capture Patients information', + name: 'rsa.identity.middlename', + type: 'keyword', + }, + 'rsa.identity.password': { + category: 'rsa', + description: 'This key is for Passwords seen in any session, plain text or encrypted', + name: 'rsa.identity.password', + type: 'keyword', + }, + 'rsa.identity.host_role': { + category: 'rsa', + description: 'This key should only be used to capture the role of a Host Machine', + name: 'rsa.identity.host_role', + type: 'keyword', + }, + 'rsa.identity.ldap': { + category: 'rsa', + description: + 'This key is for Uninterpreted LDAP values. Ldap Values that don’t have a clear query or response context', + name: 'rsa.identity.ldap', + type: 'keyword', + }, + 'rsa.identity.ldap_query': { + category: 'rsa', + description: 'This key is the Search criteria from an LDAP search', + name: 'rsa.identity.ldap_query', + type: 'keyword', + }, + 'rsa.identity.ldap_response': { + category: 'rsa', + description: 'This key is to capture Results from an LDAP search', + name: 'rsa.identity.ldap_response', + type: 'keyword', + }, + 'rsa.identity.owner': { + category: 'rsa', + description: + 'This is used to capture username the process or service is running as, the author of the task', + name: 'rsa.identity.owner', + type: 'keyword', + }, + 'rsa.identity.service_account': { + category: 'rsa', + description: + 'This key is a windows specific key, used for capturing name of the account a service (referenced in the event) is running under. Legacy Usage', + name: 'rsa.identity.service_account', + type: 'keyword', + }, + 'rsa.email.email_dst': { + category: 'rsa', + description: + 'This key is used to capture the Destination email address only, when the destination context is not clear use email', + name: 'rsa.email.email_dst', + type: 'keyword', + }, + 'rsa.email.email_src': { + category: 'rsa', + description: + 'This key is used to capture the source email address only, when the source context is not clear use email', + name: 'rsa.email.email_src', + type: 'keyword', + }, + 'rsa.email.subject': { + category: 'rsa', + description: 'This key is used to capture the subject string from an Email only.', + name: 'rsa.email.subject', + type: 'keyword', + }, + 'rsa.email.email': { + category: 'rsa', + description: + 'This key is used to capture a generic email address where the source or destination context is not clear', + name: 'rsa.email.email', + type: 'keyword', + }, + 'rsa.email.trans_from': { + category: 'rsa', + description: 'Deprecated key defined only in table map.', + name: 'rsa.email.trans_from', + type: 'keyword', + }, + 'rsa.email.trans_to': { + category: 'rsa', + description: 'Deprecated key defined only in table map.', + name: 'rsa.email.trans_to', + type: 'keyword', + }, + 'rsa.file.privilege': { + category: 'rsa', + description: 'Deprecated, use permissions', + name: 'rsa.file.privilege', + type: 'keyword', + }, + 'rsa.file.attachment': { + category: 'rsa', + description: 'This key captures the attachment file name', + name: 'rsa.file.attachment', + type: 'keyword', + }, + 'rsa.file.filesystem': { + category: 'rsa', + name: 'rsa.file.filesystem', + type: 'keyword', + }, + 'rsa.file.binary': { + category: 'rsa', + description: 'Deprecated key defined only in table map.', + name: 'rsa.file.binary', + type: 'keyword', + }, + 'rsa.file.filename_dst': { + category: 'rsa', + description: 'This is used to capture name of the file targeted by the action', + name: 'rsa.file.filename_dst', + type: 'keyword', + }, + 'rsa.file.filename_src': { + category: 'rsa', + description: + 'This is used to capture name of the parent filename, the file which performed the action', + name: 'rsa.file.filename_src', + type: 'keyword', + }, + 'rsa.file.filename_tmp': { + category: 'rsa', + name: 'rsa.file.filename_tmp', + type: 'keyword', + }, + 'rsa.file.directory_dst': { + category: 'rsa', + description: + 'This key is used to capture the directory of the target process or file', + name: 'rsa.file.directory_dst', + type: 'keyword', + }, + 'rsa.file.directory_src': { + category: 'rsa', + description: 'This key is used to capture the directory of the source process or file', + name: 'rsa.file.directory_src', + type: 'keyword', + }, + 'rsa.file.file_entropy': { + category: 'rsa', + description: 'This is used to capture entropy vale of a file', + name: 'rsa.file.file_entropy', + type: 'double', + }, + 'rsa.file.file_vendor': { + category: 'rsa', + description: 'This is used to capture Company name of file located in version_info', + name: 'rsa.file.file_vendor', + type: 'keyword', + }, + 'rsa.file.task_name': { + category: 'rsa', + description: 'This is used to capture name of the task', + name: 'rsa.file.task_name', + type: 'keyword', + }, + 'rsa.web.fqdn': { + category: 'rsa', + description: 'Fully Qualified Domain Names', + name: 'rsa.web.fqdn', + type: 'keyword', + }, + 'rsa.web.web_cookie': { + category: 'rsa', + description: 'This key is used to capture the Web cookies specifically.', + name: 'rsa.web.web_cookie', + type: 'keyword', + }, + 'rsa.web.alias_host': { + category: 'rsa', + name: 'rsa.web.alias_host', + type: 'keyword', + }, + 'rsa.web.reputation_num': { + category: 'rsa', + description: 'Reputation Number of an entity. Typically used for Web Domains', + name: 'rsa.web.reputation_num', + type: 'double', + }, + 'rsa.web.web_ref_domain': { + category: 'rsa', + description: "Web referer's domain", + name: 'rsa.web.web_ref_domain', + type: 'keyword', + }, + 'rsa.web.web_ref_query': { + category: 'rsa', + description: "This key captures Web referer's query portion of the URL", + name: 'rsa.web.web_ref_query', + type: 'keyword', + }, + 'rsa.web.remote_domain': { + category: 'rsa', + name: 'rsa.web.remote_domain', + type: 'keyword', + }, + 'rsa.web.web_ref_page': { + category: 'rsa', + description: "This key captures Web referer's page information", + name: 'rsa.web.web_ref_page', + type: 'keyword', + }, + 'rsa.web.web_ref_root': { + category: 'rsa', + description: "Web referer's root URL path", + name: 'rsa.web.web_ref_root', + type: 'keyword', + }, + 'rsa.web.cn_asn_dst': { + category: 'rsa', + name: 'rsa.web.cn_asn_dst', + type: 'keyword', + }, + 'rsa.web.cn_rpackets': { + category: 'rsa', + name: 'rsa.web.cn_rpackets', + type: 'keyword', + }, + 'rsa.web.urlpage': { + category: 'rsa', + name: 'rsa.web.urlpage', + type: 'keyword', + }, + 'rsa.web.urlroot': { + category: 'rsa', + name: 'rsa.web.urlroot', + type: 'keyword', + }, + 'rsa.web.p_url': { + category: 'rsa', + name: 'rsa.web.p_url', + type: 'keyword', + }, + 'rsa.web.p_user_agent': { + category: 'rsa', + name: 'rsa.web.p_user_agent', + type: 'keyword', + }, + 'rsa.web.p_web_cookie': { + category: 'rsa', + name: 'rsa.web.p_web_cookie', + type: 'keyword', + }, + 'rsa.web.p_web_method': { + category: 'rsa', + name: 'rsa.web.p_web_method', + type: 'keyword', + }, + 'rsa.web.p_web_referer': { + category: 'rsa', + name: 'rsa.web.p_web_referer', + type: 'keyword', + }, + 'rsa.web.web_extension_tmp': { + category: 'rsa', + name: 'rsa.web.web_extension_tmp', + type: 'keyword', + }, + 'rsa.web.web_page': { + category: 'rsa', + name: 'rsa.web.web_page', + type: 'keyword', + }, + 'rsa.threat.threat_category': { + category: 'rsa', + description: 'This key captures Threat Name/Threat Category/Categorization of alert', + name: 'rsa.threat.threat_category', + type: 'keyword', + }, + 'rsa.threat.threat_desc': { + category: 'rsa', + description: + 'This key is used to capture the threat description from the session directly or inferred', + name: 'rsa.threat.threat_desc', + type: 'keyword', + }, + 'rsa.threat.alert': { + category: 'rsa', + description: 'This key is used to capture name of the alert', + name: 'rsa.threat.alert', + type: 'keyword', + }, + 'rsa.threat.threat_source': { + category: 'rsa', + description: 'This key is used to capture source of the threat', + name: 'rsa.threat.threat_source', + type: 'keyword', + }, + 'rsa.crypto.crypto': { + category: 'rsa', + description: 'This key is used to capture the Encryption Type or Encryption Key only', + name: 'rsa.crypto.crypto', + type: 'keyword', + }, + 'rsa.crypto.cipher_src': { + category: 'rsa', + description: 'This key is for Source (Client) Cipher', + name: 'rsa.crypto.cipher_src', + type: 'keyword', + }, + 'rsa.crypto.cert_subject': { + category: 'rsa', + description: 'This key is used to capture the Certificate organization only', + name: 'rsa.crypto.cert_subject', + type: 'keyword', + }, + 'rsa.crypto.peer': { + category: 'rsa', + description: "This key is for Encryption peer's IP Address", + name: 'rsa.crypto.peer', + type: 'keyword', + }, + 'rsa.crypto.cipher_size_src': { + category: 'rsa', + description: 'This key captures Source (Client) Cipher Size', + name: 'rsa.crypto.cipher_size_src', + type: 'long', + }, + 'rsa.crypto.ike': { + category: 'rsa', + description: 'IKE negotiation phase.', + name: 'rsa.crypto.ike', + type: 'keyword', + }, + 'rsa.crypto.scheme': { + category: 'rsa', + description: 'This key captures the Encryption scheme used', + name: 'rsa.crypto.scheme', + type: 'keyword', + }, + 'rsa.crypto.peer_id': { + category: 'rsa', + description: 'This key is for Encryption peer’s identity', + name: 'rsa.crypto.peer_id', + type: 'keyword', + }, + 'rsa.crypto.sig_type': { + category: 'rsa', + description: 'This key captures the Signature Type', + name: 'rsa.crypto.sig_type', + type: 'keyword', + }, + 'rsa.crypto.cert_issuer': { + category: 'rsa', + name: 'rsa.crypto.cert_issuer', + type: 'keyword', + }, + 'rsa.crypto.cert_host_name': { + category: 'rsa', + description: 'Deprecated key defined only in table map.', + name: 'rsa.crypto.cert_host_name', + type: 'keyword', + }, + 'rsa.crypto.cert_error': { + category: 'rsa', + description: 'This key captures the Certificate Error String', + name: 'rsa.crypto.cert_error', + type: 'keyword', + }, + 'rsa.crypto.cipher_dst': { + category: 'rsa', + description: 'This key is for Destination (Server) Cipher', + name: 'rsa.crypto.cipher_dst', + type: 'keyword', + }, + 'rsa.crypto.cipher_size_dst': { + category: 'rsa', + description: 'This key captures Destination (Server) Cipher Size', + name: 'rsa.crypto.cipher_size_dst', + type: 'long', + }, + 'rsa.crypto.ssl_ver_src': { + category: 'rsa', + description: 'Deprecated, use version', + name: 'rsa.crypto.ssl_ver_src', + type: 'keyword', + }, + 'rsa.crypto.d_certauth': { + category: 'rsa', + name: 'rsa.crypto.d_certauth', + type: 'keyword', + }, + 'rsa.crypto.s_certauth': { + category: 'rsa', + name: 'rsa.crypto.s_certauth', + type: 'keyword', + }, + 'rsa.crypto.ike_cookie1': { + category: 'rsa', + description: 'ID of the negotiation — sent for ISAKMP Phase One', + name: 'rsa.crypto.ike_cookie1', + type: 'keyword', + }, + 'rsa.crypto.ike_cookie2': { + category: 'rsa', + description: 'ID of the negotiation — sent for ISAKMP Phase Two', + name: 'rsa.crypto.ike_cookie2', + type: 'keyword', + }, + 'rsa.crypto.cert_checksum': { + category: 'rsa', + name: 'rsa.crypto.cert_checksum', + type: 'keyword', + }, + 'rsa.crypto.cert_host_cat': { + category: 'rsa', + description: 'This key is used for the hostname category value of a certificate', + name: 'rsa.crypto.cert_host_cat', + type: 'keyword', + }, + 'rsa.crypto.cert_serial': { + category: 'rsa', + description: 'This key is used to capture the Certificate serial number only', + name: 'rsa.crypto.cert_serial', + type: 'keyword', + }, + 'rsa.crypto.cert_status': { + category: 'rsa', + description: 'This key captures Certificate validation status', + name: 'rsa.crypto.cert_status', + type: 'keyword', + }, + 'rsa.crypto.ssl_ver_dst': { + category: 'rsa', + description: 'Deprecated, use version', + name: 'rsa.crypto.ssl_ver_dst', + type: 'keyword', + }, + 'rsa.crypto.cert_keysize': { + category: 'rsa', + name: 'rsa.crypto.cert_keysize', + type: 'keyword', + }, + 'rsa.crypto.cert_username': { + category: 'rsa', + name: 'rsa.crypto.cert_username', + type: 'keyword', + }, + 'rsa.crypto.https_insact': { + category: 'rsa', + name: 'rsa.crypto.https_insact', + type: 'keyword', + }, + 'rsa.crypto.https_valid': { + category: 'rsa', + name: 'rsa.crypto.https_valid', + type: 'keyword', + }, + 'rsa.crypto.cert_ca': { + category: 'rsa', + description: 'This key is used to capture the Certificate signing authority only', + name: 'rsa.crypto.cert_ca', + type: 'keyword', + }, + 'rsa.crypto.cert_common': { + category: 'rsa', + description: 'This key is used to capture the Certificate common name only', + name: 'rsa.crypto.cert_common', + type: 'keyword', + }, + 'rsa.wireless.wlan_ssid': { + category: 'rsa', + description: 'This key is used to capture the ssid of a Wireless Session', + name: 'rsa.wireless.wlan_ssid', + type: 'keyword', + }, + 'rsa.wireless.access_point': { + category: 'rsa', + description: 'This key is used to capture the access point name.', + name: 'rsa.wireless.access_point', + type: 'keyword', + }, + 'rsa.wireless.wlan_channel': { + category: 'rsa', + description: 'This is used to capture the channel names', + name: 'rsa.wireless.wlan_channel', + type: 'long', + }, + 'rsa.wireless.wlan_name': { + category: 'rsa', + description: 'This key captures either WLAN number/name', + name: 'rsa.wireless.wlan_name', + type: 'keyword', + }, + 'rsa.storage.disk_volume': { + category: 'rsa', + description: 'A unique name assigned to logical units (volumes) within a physical disk', + name: 'rsa.storage.disk_volume', + type: 'keyword', + }, + 'rsa.storage.lun': { + category: 'rsa', + description: 'Logical Unit Number.This key is a very useful concept in Storage.', + name: 'rsa.storage.lun', + type: 'keyword', + }, + 'rsa.storage.pwwn': { + category: 'rsa', + description: 'This uniquely identifies a port on a HBA.', + name: 'rsa.storage.pwwn', + type: 'keyword', + }, + 'rsa.physical.org_dst': { + category: 'rsa', + description: + 'This is used to capture the destination organization based on the GEOPIP Maxmind database.', + name: 'rsa.physical.org_dst', + type: 'keyword', + }, + 'rsa.physical.org_src': { + category: 'rsa', + description: + 'This is used to capture the source organization based on the GEOPIP Maxmind database.', + name: 'rsa.physical.org_src', + type: 'keyword', + }, + 'rsa.healthcare.patient_fname': { + category: 'rsa', + description: + 'This key is for First Names only, this is used for Healthcare predominantly to capture Patients information', + name: 'rsa.healthcare.patient_fname', + type: 'keyword', + }, + 'rsa.healthcare.patient_id': { + category: 'rsa', + description: 'This key captures the unique ID for a patient', + name: 'rsa.healthcare.patient_id', + type: 'keyword', + }, + 'rsa.healthcare.patient_lname': { + category: 'rsa', + description: + 'This key is for Last Names only, this is used for Healthcare predominantly to capture Patients information', + name: 'rsa.healthcare.patient_lname', + type: 'keyword', + }, + 'rsa.healthcare.patient_mname': { + category: 'rsa', + description: + 'This key is for Middle Names only, this is used for Healthcare predominantly to capture Patients information', + name: 'rsa.healthcare.patient_mname', + type: 'keyword', + }, + 'rsa.endpoint.host_state': { + category: 'rsa', + description: + 'This key is used to capture the current state of the machine, such as blacklisted, infected, firewall disabled and so on', + name: 'rsa.endpoint.host_state', + type: 'keyword', + }, + 'rsa.endpoint.registry_key': { + category: 'rsa', + description: 'This key captures the path to the registry key', + name: 'rsa.endpoint.registry_key', + type: 'keyword', + }, + 'rsa.endpoint.registry_value': { + category: 'rsa', + description: 'This key captures values or decorators used within a registry entry', + name: 'rsa.endpoint.registry_value', + type: 'keyword', + }, + 'forcepoint.virus_id': { + category: 'forcepoint', + description: 'Virus ID ', + name: 'forcepoint.virus_id', + type: 'keyword', + }, + 'checkpoint.app_risk': { + category: 'checkpoint', + description: 'Application risk.', + name: 'checkpoint.app_risk', + type: 'keyword', + }, + 'checkpoint.app_severity': { + category: 'checkpoint', + description: 'Application threat severity.', + name: 'checkpoint.app_severity', + type: 'keyword', + }, + 'checkpoint.app_sig_id': { + category: 'checkpoint', + description: 'The signature ID which the application was detected by.', + name: 'checkpoint.app_sig_id', + type: 'keyword', + }, + 'checkpoint.auth_method': { + category: 'checkpoint', + description: 'Password authentication protocol used.', + name: 'checkpoint.auth_method', + type: 'keyword', + }, + 'checkpoint.category': { + category: 'checkpoint', + description: 'Category.', + name: 'checkpoint.category', + type: 'keyword', + }, + 'checkpoint.confidence_level': { + category: 'checkpoint', + description: 'Confidence level determined.', + name: 'checkpoint.confidence_level', + type: 'integer', + }, + 'checkpoint.connectivity_state': { + category: 'checkpoint', + description: 'Connectivity state.', + name: 'checkpoint.connectivity_state', + type: 'keyword', + }, + 'checkpoint.cookie': { + category: 'checkpoint', + description: 'IKE cookie.', + name: 'checkpoint.cookie', + type: 'keyword', + }, + 'checkpoint.dst_phone_number': { + category: 'checkpoint', + description: 'Destination IP-Phone.', + name: 'checkpoint.dst_phone_number', + type: 'keyword', + }, + 'checkpoint.email_control': { + category: 'checkpoint', + description: 'Engine name.', + name: 'checkpoint.email_control', + type: 'keyword', + }, + 'checkpoint.email_id': { + category: 'checkpoint', + description: 'Internal email ID.', + name: 'checkpoint.email_id', + type: 'keyword', + }, + 'checkpoint.email_recipients_num': { + category: 'checkpoint', + description: 'Number of recipients.', + name: 'checkpoint.email_recipients_num', + type: 'long', + }, + 'checkpoint.email_session_id': { + category: 'checkpoint', + description: 'Internal email session ID.', + name: 'checkpoint.email_session_id', + type: 'keyword', + }, + 'checkpoint.email_spool_id': { + category: 'checkpoint', + description: 'Internal email spool ID.', + name: 'checkpoint.email_spool_id', + type: 'keyword', + }, + 'checkpoint.email_subject': { + category: 'checkpoint', + description: 'Email subject.', + name: 'checkpoint.email_subject', + type: 'keyword', + }, + 'checkpoint.event_count': { + category: 'checkpoint', + description: 'Number of events associated with the log.', + name: 'checkpoint.event_count', + type: 'long', + }, + 'checkpoint.frequency': { + category: 'checkpoint', + description: 'Scan frequency.', + name: 'checkpoint.frequency', + type: 'keyword', + }, + 'checkpoint.icmp_type': { + category: 'checkpoint', + description: 'ICMP type.', + name: 'checkpoint.icmp_type', + type: 'long', + }, + 'checkpoint.icmp_code': { + category: 'checkpoint', + description: 'ICMP code.', + name: 'checkpoint.icmp_code', + type: 'long', + }, + 'checkpoint.identity_type': { + category: 'checkpoint', + description: 'Identity type.', + name: 'checkpoint.identity_type', + type: 'keyword', + }, + 'checkpoint.incident_extension': { + category: 'checkpoint', + description: 'Format of original data.', + name: 'checkpoint.incident_extension', + type: 'keyword', + }, + 'checkpoint.integrity_av_invoke_type': { + category: 'checkpoint', + description: 'Scan invoke type.', + name: 'checkpoint.integrity_av_invoke_type', + type: 'keyword', + }, + 'checkpoint.malware_family': { + category: 'checkpoint', + description: 'Malware family.', + name: 'checkpoint.malware_family', + type: 'keyword', + }, + 'checkpoint.peer_gateway': { + category: 'checkpoint', + description: 'Main IP of the peer Security Gateway.', + name: 'checkpoint.peer_gateway', + type: 'ip', + }, + 'checkpoint.performance_impact': { + category: 'checkpoint', + description: 'Protection performance impact.', + name: 'checkpoint.performance_impact', + type: 'integer', + }, + 'checkpoint.protection_id': { + category: 'checkpoint', + description: 'Protection malware ID.', + name: 'checkpoint.protection_id', + type: 'keyword', + }, + 'checkpoint.protection_name': { + category: 'checkpoint', + description: 'Specific signature name of the attack.', + name: 'checkpoint.protection_name', + type: 'keyword', + }, + 'checkpoint.protection_type': { + category: 'checkpoint', + description: 'Type of protection used to detect the attack.', + name: 'checkpoint.protection_type', + type: 'keyword', + }, + 'checkpoint.scan_result': { + category: 'checkpoint', + description: 'Scan result.', + name: 'checkpoint.scan_result', + type: 'keyword', + }, + 'checkpoint.sensor_mode': { + category: 'checkpoint', + description: 'Sensor mode.', + name: 'checkpoint.sensor_mode', + type: 'keyword', + }, + 'checkpoint.severity': { + category: 'checkpoint', + description: 'Threat severity.', + name: 'checkpoint.severity', + type: 'keyword', + }, + 'checkpoint.spyware_name': { + category: 'checkpoint', + description: 'Spyware name.', + name: 'checkpoint.spyware_name', + type: 'keyword', + }, + 'checkpoint.spyware_status': { + category: 'checkpoint', + description: 'Spyware status.', + name: 'checkpoint.spyware_status', + type: 'keyword', + }, + 'checkpoint.subs_exp': { + category: 'checkpoint', + description: 'The expiration date of the subscription.', + name: 'checkpoint.subs_exp', + type: 'date', + }, + 'checkpoint.tcp_flags': { + category: 'checkpoint', + description: 'TCP packet flags.', + name: 'checkpoint.tcp_flags', + type: 'keyword', + }, + 'checkpoint.termination_reason': { + category: 'checkpoint', + description: 'Termination reason.', + name: 'checkpoint.termination_reason', + type: 'keyword', + }, + 'checkpoint.update_status': { + category: 'checkpoint', + description: 'Update status.', + name: 'checkpoint.update_status', + type: 'keyword', + }, + 'checkpoint.user_status': { + category: 'checkpoint', + description: 'User response.', + name: 'checkpoint.user_status', + type: 'keyword', + }, + 'checkpoint.uuid': { + category: 'checkpoint', + description: 'External ID.', + name: 'checkpoint.uuid', + type: 'keyword', + }, + 'checkpoint.virus_name': { + category: 'checkpoint', + description: 'Virus name.', + name: 'checkpoint.virus_name', + type: 'keyword', + }, + 'checkpoint.voip_log_type': { + category: 'checkpoint', + description: 'VoIP log types.', + name: 'checkpoint.voip_log_type', + type: 'keyword', + }, + 'cef.extensions.cp_app_risk': { + category: 'cef', + name: 'cef.extensions.cp_app_risk', + type: 'keyword', + }, + 'cef.extensions.cp_severity': { + category: 'cef', + name: 'cef.extensions.cp_severity', + type: 'keyword', + }, + 'cef.extensions.ifname': { + category: 'cef', + name: 'cef.extensions.ifname', + type: 'keyword', + }, + 'cef.extensions.inzone': { + category: 'cef', + name: 'cef.extensions.inzone', + type: 'keyword', + }, + 'cef.extensions.layer_uuid': { + category: 'cef', + name: 'cef.extensions.layer_uuid', + type: 'keyword', + }, + 'cef.extensions.layer_name': { + category: 'cef', + name: 'cef.extensions.layer_name', + type: 'keyword', + }, + 'cef.extensions.logid': { + category: 'cef', + name: 'cef.extensions.logid', + type: 'keyword', + }, + 'cef.extensions.loguid': { + category: 'cef', + name: 'cef.extensions.loguid', + type: 'keyword', + }, + 'cef.extensions.match_id': { + category: 'cef', + name: 'cef.extensions.match_id', + type: 'keyword', + }, + 'cef.extensions.nat_addtnl_rulenum': { + category: 'cef', + name: 'cef.extensions.nat_addtnl_rulenum', + type: 'keyword', + }, + 'cef.extensions.nat_rulenum': { + category: 'cef', + name: 'cef.extensions.nat_rulenum', + type: 'keyword', + }, + 'cef.extensions.origin': { + category: 'cef', + name: 'cef.extensions.origin', + type: 'keyword', + }, + 'cef.extensions.originsicname': { + category: 'cef', + name: 'cef.extensions.originsicname', + type: 'keyword', + }, + 'cef.extensions.outzone': { + category: 'cef', + name: 'cef.extensions.outzone', + type: 'keyword', + }, + 'cef.extensions.parent_rule': { + category: 'cef', + name: 'cef.extensions.parent_rule', + type: 'keyword', + }, + 'cef.extensions.product': { + category: 'cef', + name: 'cef.extensions.product', + type: 'keyword', + }, + 'cef.extensions.rule_action': { + category: 'cef', + name: 'cef.extensions.rule_action', + type: 'keyword', + }, + 'cef.extensions.rule_uid': { + category: 'cef', + name: 'cef.extensions.rule_uid', + type: 'keyword', + }, + 'cef.extensions.sequencenum': { + category: 'cef', + name: 'cef.extensions.sequencenum', + type: 'keyword', + }, + 'cef.extensions.service_id': { + category: 'cef', + name: 'cef.extensions.service_id', + type: 'keyword', + }, + 'cef.extensions.version': { + category: 'cef', + name: 'cef.extensions.version', + type: 'keyword', + }, + 'checkpoint.calc_desc': { + category: 'checkpoint', + description: 'Log description. ', + name: 'checkpoint.calc_desc', + type: 'keyword', + }, + 'checkpoint.dst_country': { + category: 'checkpoint', + description: 'Destination country. ', + name: 'checkpoint.dst_country', + type: 'keyword', + }, + 'checkpoint.dst_user_name': { + category: 'checkpoint', + description: 'Connected user name on the destination IP. ', + name: 'checkpoint.dst_user_name', + type: 'keyword', + }, + 'checkpoint.sys_message': { + category: 'checkpoint', + description: 'System messages ', + name: 'checkpoint.sys_message', + type: 'keyword', + }, + 'checkpoint.logid': { + category: 'checkpoint', + description: 'System messages ', + name: 'checkpoint.logid', + type: 'keyword', + }, + 'checkpoint.failure_impact': { + category: 'checkpoint', + description: 'The impact of update service failure. ', + name: 'checkpoint.failure_impact', + type: 'keyword', + }, + 'checkpoint.id': { + category: 'checkpoint', + description: 'Override application ID. ', + name: 'checkpoint.id', + type: 'integer', + }, + 'checkpoint.information': { + category: 'checkpoint', + description: 'Policy installation status for a specific blade. ', + name: 'checkpoint.information', + type: 'keyword', + }, + 'checkpoint.layer_name': { + category: 'checkpoint', + description: 'Layer name. ', + name: 'checkpoint.layer_name', + type: 'keyword', + }, + 'checkpoint.layer_uuid': { + category: 'checkpoint', + description: 'Layer UUID. ', + name: 'checkpoint.layer_uuid', + type: 'keyword', + }, + 'checkpoint.log_id': { + category: 'checkpoint', + description: 'Unique identity for logs. ', + name: 'checkpoint.log_id', + type: 'integer', + }, + 'checkpoint.origin_sic_name': { + category: 'checkpoint', + description: 'Machine SIC. ', + name: 'checkpoint.origin_sic_name', + type: 'keyword', + }, + 'checkpoint.policy_mgmt': { + category: 'checkpoint', + description: 'Name of the Management Server that manages this Security Gateway. ', + name: 'checkpoint.policy_mgmt', + type: 'keyword', + }, + 'checkpoint.policy_name': { + category: 'checkpoint', + description: 'Name of the last policy that this Security Gateway fetched. ', + name: 'checkpoint.policy_name', + type: 'keyword', + }, + 'checkpoint.protocol': { + category: 'checkpoint', + description: 'Protocol detected on the connection. ', + name: 'checkpoint.protocol', + type: 'keyword', + }, + 'checkpoint.proxy_src_ip': { + category: 'checkpoint', + description: 'Sender source IP (even when using proxy). ', + name: 'checkpoint.proxy_src_ip', + type: 'ip', + }, + 'checkpoint.rule': { + category: 'checkpoint', + description: 'Matched rule number. ', + name: 'checkpoint.rule', + type: 'integer', + }, + 'checkpoint.rule_action': { + category: 'checkpoint', + description: 'Action of the matched rule in the access policy. ', + name: 'checkpoint.rule_action', + type: 'keyword', + }, + 'checkpoint.scan_direction': { + category: 'checkpoint', + description: 'Scan direction. ', + name: 'checkpoint.scan_direction', + type: 'keyword', + }, + 'checkpoint.session_id': { + category: 'checkpoint', + description: 'Log uuid. ', + name: 'checkpoint.session_id', + type: 'keyword', + }, + 'checkpoint.source_os': { + category: 'checkpoint', + description: 'OS which generated the attack. ', + name: 'checkpoint.source_os', + type: 'keyword', + }, + 'checkpoint.src_country': { + category: 'checkpoint', + description: 'Country name, derived from connection source IP address. ', + name: 'checkpoint.src_country', + type: 'keyword', + }, + 'checkpoint.src_user_name': { + category: 'checkpoint', + description: 'User name connected to source IP ', + name: 'checkpoint.src_user_name', + type: 'keyword', + }, + 'checkpoint.ticket_id': { + category: 'checkpoint', + description: 'Unique ID per file. ', + name: 'checkpoint.ticket_id', + type: 'keyword', + }, + 'checkpoint.tls_server_host_name': { + category: 'checkpoint', + description: 'SNI/CN from encrypted TLS connection used by URLF for categorization. ', + name: 'checkpoint.tls_server_host_name', + type: 'keyword', + }, + 'checkpoint.verdict': { + category: 'checkpoint', + description: 'TE engine verdict Possible values: Malicious/Benign/Error. ', + name: 'checkpoint.verdict', + type: 'keyword', + }, + 'checkpoint.user': { + category: 'checkpoint', + description: 'Source user name. ', + name: 'checkpoint.user', + type: 'keyword', + }, + 'checkpoint.vendor_list': { + category: 'checkpoint', + description: 'The vendor name that provided the verdict for a malicious URL. ', + name: 'checkpoint.vendor_list', + type: 'keyword', + }, + 'checkpoint.web_server_type': { + category: 'checkpoint', + description: 'Web server detected in the HTTP response. ', + name: 'checkpoint.web_server_type', + type: 'keyword', + }, + 'checkpoint.client_name': { + category: 'checkpoint', + description: 'Client Application or Software Blade that detected the event. ', + name: 'checkpoint.client_name', + type: 'keyword', + }, + 'checkpoint.client_version': { + category: 'checkpoint', + description: 'Build version of SandBlast Agent client installed on the computer. ', + name: 'checkpoint.client_version', + type: 'keyword', + }, + 'checkpoint.extension_version': { + category: 'checkpoint', + description: 'Build version of the SandBlast Agent browser extension. ', + name: 'checkpoint.extension_version', + type: 'keyword', + }, + 'checkpoint.host_time': { + category: 'checkpoint', + description: 'Local time on the endpoint computer. ', + name: 'checkpoint.host_time', + type: 'keyword', + }, + 'checkpoint.installed_products': { + category: 'checkpoint', + description: 'List of installed Endpoint Software Blades. ', + name: 'checkpoint.installed_products', + type: 'keyword', + }, + 'checkpoint.cc': { + category: 'checkpoint', + description: 'The Carbon Copy address of the email. ', + name: 'checkpoint.cc', + type: 'keyword', + }, + 'checkpoint.parent_process_username': { + category: 'checkpoint', + description: 'Owner username of the parent process of the process that triggered the attack. ', + name: 'checkpoint.parent_process_username', + type: 'keyword', + }, + 'checkpoint.process_username': { + category: 'checkpoint', + description: 'Owner username of the process that triggered the attack. ', + name: 'checkpoint.process_username', + type: 'keyword', + }, + 'checkpoint.audit_status': { + category: 'checkpoint', + description: 'Audit Status. Can be Success or Failure. ', + name: 'checkpoint.audit_status', + type: 'keyword', + }, + 'checkpoint.objecttable': { + category: 'checkpoint', + description: 'Table of affected objects. ', + name: 'checkpoint.objecttable', + type: 'keyword', + }, + 'checkpoint.objecttype': { + category: 'checkpoint', + description: 'The type of the affected object. ', + name: 'checkpoint.objecttype', + type: 'keyword', + }, + 'checkpoint.operation_number': { + category: 'checkpoint', + description: 'The operation nuber. ', + name: 'checkpoint.operation_number', + type: 'keyword', + }, + 'checkpoint.suppressed_logs': { + category: 'checkpoint', + description: + 'Aggregated connections for five minutes on the same source, destination and port. ', + name: 'checkpoint.suppressed_logs', + type: 'integer', + }, + 'checkpoint.blade_name': { + category: 'checkpoint', + description: 'Blade name. ', + name: 'checkpoint.blade_name', + type: 'keyword', + }, + 'checkpoint.status': { + category: 'checkpoint', + description: 'Ok/Warning/Error. ', + name: 'checkpoint.status', + type: 'keyword', + }, + 'checkpoint.short_desc': { + category: 'checkpoint', + description: 'Short description of the process that was executed. ', + name: 'checkpoint.short_desc', + type: 'keyword', + }, + 'checkpoint.long_desc': { + category: 'checkpoint', + description: 'More information on the process (usually describing error reason in failure). ', + name: 'checkpoint.long_desc', + type: 'keyword', + }, + 'checkpoint.scan_hosts_hour': { + category: 'checkpoint', + description: 'Number of unique hosts during the last hour. ', + name: 'checkpoint.scan_hosts_hour', + type: 'integer', + }, + 'checkpoint.scan_hosts_day': { + category: 'checkpoint', + description: 'Number of unique hosts during the last day. ', + name: 'checkpoint.scan_hosts_day', + type: 'integer', + }, + 'checkpoint.scan_hosts_week': { + category: 'checkpoint', + description: 'Number of unique hosts during the last week. ', + name: 'checkpoint.scan_hosts_week', + type: 'integer', + }, + 'checkpoint.unique_detected_hour': { + category: 'checkpoint', + description: 'Detected virus for a specific host during the last hour. ', + name: 'checkpoint.unique_detected_hour', + type: 'integer', + }, + 'checkpoint.unique_detected_day': { + category: 'checkpoint', + description: 'Detected virus for a specific host during the last day. ', + name: 'checkpoint.unique_detected_day', + type: 'integer', + }, + 'checkpoint.unique_detected_week': { + category: 'checkpoint', + description: 'Detected virus for a specific host during the last week. ', + name: 'checkpoint.unique_detected_week', + type: 'integer', + }, + 'checkpoint.scan_mail': { + category: 'checkpoint', + description: 'Number of emails that were scanned by "AB malicious activity" engine. ', + name: 'checkpoint.scan_mail', + type: 'integer', + }, + 'checkpoint.additional_ip': { + category: 'checkpoint', + description: 'DNS host name. ', + name: 'checkpoint.additional_ip', + type: 'keyword', + }, + 'checkpoint.description': { + category: 'checkpoint', + description: 'Additional explanation how the security gateway enforced the connection. ', + name: 'checkpoint.description', + type: 'keyword', + }, + 'checkpoint.email_spam_category': { + category: 'checkpoint', + description: 'Email categories. Possible values: spam/not spam/phishing. ', + name: 'checkpoint.email_spam_category', + type: 'keyword', + }, + 'checkpoint.email_control_analysis': { + category: 'checkpoint', + description: 'Message classification, received from spam vendor engine. ', + name: 'checkpoint.email_control_analysis', + type: 'keyword', + }, + 'checkpoint.scan_results': { + category: 'checkpoint', + description: '"Infected"/description of a failure. ', + name: 'checkpoint.scan_results', + type: 'keyword', + }, + 'checkpoint.original_queue_id': { + category: 'checkpoint', + description: 'Original postfix email queue id. ', + name: 'checkpoint.original_queue_id', + type: 'keyword', + }, + 'checkpoint.risk': { + category: 'checkpoint', + description: 'Risk level we got from the engine. ', + name: 'checkpoint.risk', + type: 'keyword', + }, + 'checkpoint.observable_name': { + category: 'checkpoint', + description: 'IOC observable signature name. ', + name: 'checkpoint.observable_name', + type: 'keyword', + }, + 'checkpoint.observable_id': { + category: 'checkpoint', + description: 'IOC observable signature id. ', + name: 'checkpoint.observable_id', + type: 'keyword', + }, + 'checkpoint.observable_comment': { + category: 'checkpoint', + description: 'IOC observable signature description. ', + name: 'checkpoint.observable_comment', + type: 'keyword', + }, + 'checkpoint.indicator_name': { + category: 'checkpoint', + description: 'IOC indicator name. ', + name: 'checkpoint.indicator_name', + type: 'keyword', + }, + 'checkpoint.indicator_description': { + category: 'checkpoint', + description: 'IOC indicator description. ', + name: 'checkpoint.indicator_description', + type: 'keyword', + }, + 'checkpoint.indicator_reference': { + category: 'checkpoint', + description: 'IOC indicator reference. ', + name: 'checkpoint.indicator_reference', + type: 'keyword', + }, + 'checkpoint.indicator_uuid': { + category: 'checkpoint', + description: 'IOC indicator uuid. ', + name: 'checkpoint.indicator_uuid', + type: 'keyword', + }, + 'checkpoint.app_desc': { + category: 'checkpoint', + description: 'Application description. ', + name: 'checkpoint.app_desc', + type: 'keyword', + }, + 'checkpoint.app_id': { + category: 'checkpoint', + description: 'Application ID. ', + name: 'checkpoint.app_id', + type: 'integer', + }, + 'checkpoint.certificate_resource': { + category: 'checkpoint', + description: 'HTTPS resource Possible values: SNI or domain name (DN). ', + name: 'checkpoint.certificate_resource', + type: 'keyword', + }, + 'checkpoint.certificate_validation': { + category: 'checkpoint', + description: + 'Precise error, describing HTTPS certificate failure under "HTTPS categorize websites" feature. ', + name: 'checkpoint.certificate_validation', + type: 'keyword', + }, + 'checkpoint.browse_time': { + category: 'checkpoint', + description: 'Application session browse time. ', + name: 'checkpoint.browse_time', + type: 'keyword', + }, + 'checkpoint.limit_requested': { + category: 'checkpoint', + description: 'Indicates whether data limit was requested for the session. ', + name: 'checkpoint.limit_requested', + type: 'integer', + }, + 'checkpoint.limit_applied': { + category: 'checkpoint', + description: 'Indicates whether the session was actually date limited. ', + name: 'checkpoint.limit_applied', + type: 'integer', + }, + 'checkpoint.dropped_total': { + category: 'checkpoint', + description: 'Amount of dropped packets (both incoming and outgoing). ', + name: 'checkpoint.dropped_total', + type: 'integer', + }, + 'checkpoint.client_type_os': { + category: 'checkpoint', + description: 'Client OS detected in the HTTP request. ', + name: 'checkpoint.client_type_os', + type: 'keyword', + }, + 'checkpoint.name': { + category: 'checkpoint', + description: 'Application name. ', + name: 'checkpoint.name', + type: 'keyword', + }, + 'checkpoint.properties': { + category: 'checkpoint', + description: 'Application categories. ', + name: 'checkpoint.properties', + type: 'keyword', + }, + 'checkpoint.sig_id': { + category: 'checkpoint', + description: "Application's signature ID which how it was detected by. ", + name: 'checkpoint.sig_id', + type: 'keyword', + }, + 'checkpoint.desc': { + category: 'checkpoint', + description: 'Override application description. ', + name: 'checkpoint.desc', + type: 'keyword', + }, + 'checkpoint.referrer_self_uid': { + category: 'checkpoint', + description: 'UUID of the current log. ', + name: 'checkpoint.referrer_self_uid', + type: 'keyword', + }, + 'checkpoint.referrer_parent_uid': { + category: 'checkpoint', + description: 'Log UUID of the referring application. ', + name: 'checkpoint.referrer_parent_uid', + type: 'keyword', + }, + 'checkpoint.needs_browse_time': { + category: 'checkpoint', + description: 'Browse time required for the connection. ', + name: 'checkpoint.needs_browse_time', + type: 'integer', + }, + 'checkpoint.cluster_info': { + category: 'checkpoint', + description: + 'Cluster information. Possible options: Failover reason/cluster state changes/CP cluster or 3rd party. ', + name: 'checkpoint.cluster_info', + type: 'keyword', + }, + 'checkpoint.sync': { + category: 'checkpoint', + description: 'Sync status and the reason (stable, at risk). ', + name: 'checkpoint.sync', + type: 'keyword', + }, + 'checkpoint.file_direction': { + category: 'checkpoint', + description: 'File direction. Possible options: upload/download. ', + name: 'checkpoint.file_direction', + type: 'keyword', + }, + 'checkpoint.invalid_file_size': { + category: 'checkpoint', + description: 'File_size field is valid only if this field is set to 0. ', + name: 'checkpoint.invalid_file_size', + type: 'integer', + }, + 'checkpoint.top_archive_file_name': { + category: 'checkpoint', + description: 'In case of archive file: the file that was sent/received. ', + name: 'checkpoint.top_archive_file_name', + type: 'keyword', + }, + 'checkpoint.data_type_name': { + category: 'checkpoint', + description: 'Data type in rulebase that was matched. ', + name: 'checkpoint.data_type_name', + type: 'keyword', + }, + 'checkpoint.specific_data_type_name': { + category: 'checkpoint', + description: 'Compound/Group scenario, data type that was matched. ', + name: 'checkpoint.specific_data_type_name', + type: 'keyword', + }, + 'checkpoint.word_list': { + category: 'checkpoint', + description: 'Words matched by data type. ', + name: 'checkpoint.word_list', + type: 'keyword', + }, + 'checkpoint.info': { + category: 'checkpoint', + description: 'Special log message. ', + name: 'checkpoint.info', + type: 'keyword', + }, + 'checkpoint.outgoing_url': { + category: 'checkpoint', + description: 'URL related to this log (for HTTP). ', + name: 'checkpoint.outgoing_url', + type: 'keyword', + }, + 'checkpoint.dlp_rule_name': { + category: 'checkpoint', + description: 'Matched rule name. ', + name: 'checkpoint.dlp_rule_name', + type: 'keyword', + }, + 'checkpoint.dlp_recipients': { + category: 'checkpoint', + description: 'Mail recipients. ', + name: 'checkpoint.dlp_recipients', + type: 'keyword', + }, + 'checkpoint.dlp_subject': { + category: 'checkpoint', + description: 'Mail subject. ', + name: 'checkpoint.dlp_subject', + type: 'keyword', + }, + 'checkpoint.dlp_word_list': { + category: 'checkpoint', + description: 'Phrases matched by data type. ', + name: 'checkpoint.dlp_word_list', + type: 'keyword', + }, + 'checkpoint.dlp_template_score': { + category: 'checkpoint', + description: 'Template data type match score. ', + name: 'checkpoint.dlp_template_score', + type: 'keyword', + }, + 'checkpoint.message_size': { + category: 'checkpoint', + description: 'Mail/post size. ', + name: 'checkpoint.message_size', + type: 'integer', + }, + 'checkpoint.dlp_incident_uid': { + category: 'checkpoint', + description: 'Unique ID of the matched rule. ', + name: 'checkpoint.dlp_incident_uid', + type: 'keyword', + }, + 'checkpoint.dlp_related_incident_uid': { + category: 'checkpoint', + description: 'Other ID related to this one. ', + name: 'checkpoint.dlp_related_incident_uid', + type: 'keyword', + }, + 'checkpoint.dlp_data_type_name': { + category: 'checkpoint', + description: 'Matched data type. ', + name: 'checkpoint.dlp_data_type_name', + type: 'keyword', + }, + 'checkpoint.dlp_data_type_uid': { + category: 'checkpoint', + description: 'Unique ID of the matched data type. ', + name: 'checkpoint.dlp_data_type_uid', + type: 'keyword', + }, + 'checkpoint.dlp_violation_description': { + category: 'checkpoint', + description: 'Violation descriptions described in the rulebase. ', + name: 'checkpoint.dlp_violation_description', + type: 'keyword', + }, + 'checkpoint.dlp_relevant_data_types': { + category: 'checkpoint', + description: 'In case of Compound/Group: the inner data types that were matched. ', + name: 'checkpoint.dlp_relevant_data_types', + type: 'keyword', + }, + 'checkpoint.dlp_action_reason': { + category: 'checkpoint', + description: 'Action chosen reason. ', + name: 'checkpoint.dlp_action_reason', + type: 'keyword', + }, + 'checkpoint.dlp_categories': { + category: 'checkpoint', + description: 'Data type category. ', + name: 'checkpoint.dlp_categories', + type: 'keyword', + }, + 'checkpoint.dlp_transint': { + category: 'checkpoint', + description: 'HTTP/SMTP/FTP. ', + name: 'checkpoint.dlp_transint', + type: 'keyword', + }, + 'checkpoint.duplicate': { + category: 'checkpoint', + description: + 'Log marked as duplicated, when mail is split and the Security Gateway sees it twice. ', + name: 'checkpoint.duplicate', + type: 'keyword', + }, + 'checkpoint.matched_file': { + category: 'checkpoint', + description: 'Unique ID of the matched data type. ', + name: 'checkpoint.matched_file', + type: 'keyword', + }, + 'checkpoint.matched_file_text_segments': { + category: 'checkpoint', + description: 'Fingerprint: number of text segments matched by this traffic. ', + name: 'checkpoint.matched_file_text_segments', + type: 'integer', + }, + 'checkpoint.matched_file_percentage': { + category: 'checkpoint', + description: 'Fingerprint: match percentage of the traffic. ', + name: 'checkpoint.matched_file_percentage', + type: 'integer', + }, + 'checkpoint.dlp_additional_action': { + category: 'checkpoint', + description: 'Watermark/None. ', + name: 'checkpoint.dlp_additional_action', + type: 'keyword', + }, + 'checkpoint.dlp_watermark_profile': { + category: 'checkpoint', + description: 'Watermark which was applied. ', + name: 'checkpoint.dlp_watermark_profile', + type: 'keyword', + }, + 'checkpoint.dlp_repository_id': { + category: 'checkpoint', + description: 'ID of scanned repository. ', + name: 'checkpoint.dlp_repository_id', + type: 'keyword', + }, + 'checkpoint.dlp_repository_root_path': { + category: 'checkpoint', + description: 'Repository path. ', + name: 'checkpoint.dlp_repository_root_path', + type: 'keyword', + }, + 'checkpoint.scan_id': { + category: 'checkpoint', + description: 'Sequential number of scan. ', + name: 'checkpoint.scan_id', + type: 'keyword', + }, + 'checkpoint.special_properties': { + category: 'checkpoint', + description: + "If this field is set to '1' the log will not be shown (in use for monitoring scan progress). ", + name: 'checkpoint.special_properties', + type: 'integer', + }, + 'checkpoint.dlp_repository_total_size': { + category: 'checkpoint', + description: 'Repository size. ', + name: 'checkpoint.dlp_repository_total_size', + type: 'integer', + }, + 'checkpoint.dlp_repository_files_number': { + category: 'checkpoint', + description: 'Number of files in repository. ', + name: 'checkpoint.dlp_repository_files_number', + type: 'integer', + }, + 'checkpoint.dlp_repository_scanned_files_number': { + category: 'checkpoint', + description: 'Number of scanned files in repository. ', + name: 'checkpoint.dlp_repository_scanned_files_number', + type: 'integer', + }, + 'checkpoint.duration': { + category: 'checkpoint', + description: 'Scan duration. ', + name: 'checkpoint.duration', + type: 'keyword', + }, + 'checkpoint.dlp_fingerprint_long_status': { + category: 'checkpoint', + description: 'Scan status - long format. ', + name: 'checkpoint.dlp_fingerprint_long_status', + type: 'keyword', + }, + 'checkpoint.dlp_fingerprint_short_status': { + category: 'checkpoint', + description: 'Scan status - short format. ', + name: 'checkpoint.dlp_fingerprint_short_status', + type: 'keyword', + }, + 'checkpoint.dlp_repository_directories_number': { + category: 'checkpoint', + description: 'Number of directories in repository. ', + name: 'checkpoint.dlp_repository_directories_number', + type: 'integer', + }, + 'checkpoint.dlp_repository_unreachable_directories_number': { + category: 'checkpoint', + description: 'Number of directories the Security Gateway was unable to read. ', + name: 'checkpoint.dlp_repository_unreachable_directories_number', + type: 'integer', + }, + 'checkpoint.dlp_fingerprint_files_number': { + category: 'checkpoint', + description: 'Number of successfully scanned files in repository. ', + name: 'checkpoint.dlp_fingerprint_files_number', + type: 'integer', + }, + 'checkpoint.dlp_repository_skipped_files_number': { + category: 'checkpoint', + description: 'Skipped number of files because of configuration. ', + name: 'checkpoint.dlp_repository_skipped_files_number', + type: 'integer', + }, + 'checkpoint.dlp_repository_scanned_directories_number': { + category: 'checkpoint', + description: 'Amount of directories scanned. ', + name: 'checkpoint.dlp_repository_scanned_directories_number', + type: 'integer', + }, + 'checkpoint.number_of_errors': { + category: 'checkpoint', + description: 'Number of files that were not scanned due to an error. ', + name: 'checkpoint.number_of_errors', + type: 'integer', + }, + 'checkpoint.next_scheduled_scan_date': { + category: 'checkpoint', + description: 'Next scan scheduled time according to time object. ', + name: 'checkpoint.next_scheduled_scan_date', + type: 'keyword', + }, + 'checkpoint.dlp_repository_scanned_total_size': { + category: 'checkpoint', + description: 'Size scanned. ', + name: 'checkpoint.dlp_repository_scanned_total_size', + type: 'integer', + }, + 'checkpoint.dlp_repository_reached_directories_number': { + category: 'checkpoint', + description: 'Number of scanned directories in repository. ', + name: 'checkpoint.dlp_repository_reached_directories_number', + type: 'integer', + }, + 'checkpoint.dlp_repository_not_scanned_directories_percentage': { + category: 'checkpoint', + description: 'Percentage of directories the Security Gateway was unable to read. ', + name: 'checkpoint.dlp_repository_not_scanned_directories_percentage', + type: 'integer', + }, + 'checkpoint.speed': { + category: 'checkpoint', + description: 'Current scan speed. ', + name: 'checkpoint.speed', + type: 'integer', + }, + 'checkpoint.dlp_repository_scan_progress': { + category: 'checkpoint', + description: 'Scan percentage. ', + name: 'checkpoint.dlp_repository_scan_progress', + type: 'integer', + }, + 'checkpoint.sub_policy_name': { + category: 'checkpoint', + description: 'Layer name. ', + name: 'checkpoint.sub_policy_name', + type: 'keyword', + }, + 'checkpoint.sub_policy_uid': { + category: 'checkpoint', + description: 'Layer uid. ', + name: 'checkpoint.sub_policy_uid', + type: 'keyword', + }, + 'checkpoint.fw_message': { + category: 'checkpoint', + description: 'Used for various firewall errors. ', + name: 'checkpoint.fw_message', + type: 'keyword', + }, + 'checkpoint.message': { + category: 'checkpoint', + description: 'ISP link has failed. ', + name: 'checkpoint.message', + type: 'keyword', + }, + 'checkpoint.isp_link': { + category: 'checkpoint', + description: 'Name of ISP link. ', + name: 'checkpoint.isp_link', + type: 'keyword', + }, + 'checkpoint.fw_subproduct': { + category: 'checkpoint', + description: 'Can be vpn/non vpn. ', + name: 'checkpoint.fw_subproduct', + type: 'keyword', + }, + 'checkpoint.sctp_error': { + category: 'checkpoint', + description: 'Error information, what caused sctp to fail on out_of_state. ', + name: 'checkpoint.sctp_error', + type: 'keyword', + }, + 'checkpoint.chunk_type': { + category: 'checkpoint', + description: 'Chunck of the sctp stream. ', + name: 'checkpoint.chunk_type', + type: 'keyword', + }, + 'checkpoint.sctp_association_state': { + category: 'checkpoint', + description: 'The bad state you were trying to update to. ', + name: 'checkpoint.sctp_association_state', + type: 'keyword', + }, + 'checkpoint.tcp_packet_out_of_state': { + category: 'checkpoint', + description: 'State violation. ', + name: 'checkpoint.tcp_packet_out_of_state', + type: 'keyword', + }, + 'checkpoint.connectivity_level': { + category: 'checkpoint', + description: 'Log for a new connection in wire mode. ', + name: 'checkpoint.connectivity_level', + type: 'keyword', + }, + 'checkpoint.ip_option': { + category: 'checkpoint', + description: 'IP option that was dropped. ', + name: 'checkpoint.ip_option', + type: 'integer', + }, + 'checkpoint.tcp_state': { + category: 'checkpoint', + description: 'Log reinting a tcp state change. ', + name: 'checkpoint.tcp_state', + type: 'keyword', + }, + 'checkpoint.expire_time': { + category: 'checkpoint', + description: 'Connection closing time. ', + name: 'checkpoint.expire_time', + type: 'keyword', + }, + 'checkpoint.rpc_prog': { + category: 'checkpoint', + description: 'Log for new RPC state - prog values. ', + name: 'checkpoint.rpc_prog', + type: 'integer', + }, + 'checkpoint.dce-rpc_interface_uuid': { + category: 'checkpoint', + description: 'Log for new RPC state - UUID values ', + name: 'checkpoint.dce-rpc_interface_uuid', + type: 'keyword', + }, + 'checkpoint.elapsed': { + category: 'checkpoint', + description: 'Time passed since start time. ', + name: 'checkpoint.elapsed', + type: 'keyword', + }, + 'checkpoint.icmp': { + category: 'checkpoint', + description: 'Number of packets, received by the client. ', + name: 'checkpoint.icmp', + type: 'keyword', + }, + 'checkpoint.capture_uuid': { + category: 'checkpoint', + description: 'UUID generated for the capture. Used when enabling the capture when logging. ', + name: 'checkpoint.capture_uuid', + type: 'keyword', + }, + 'checkpoint.diameter_app_ID': { + category: 'checkpoint', + description: 'The ID of diameter application. ', + name: 'checkpoint.diameter_app_ID', + type: 'integer', + }, + 'checkpoint.diameter_cmd_code': { + category: 'checkpoint', + description: 'Diameter not allowed application command id. ', + name: 'checkpoint.diameter_cmd_code', + type: 'integer', + }, + 'checkpoint.diameter_msg_type': { + category: 'checkpoint', + description: 'Diameter message type. ', + name: 'checkpoint.diameter_msg_type', + type: 'keyword', + }, + 'checkpoint.cp_message': { + category: 'checkpoint', + description: 'Used to log a general message. ', + name: 'checkpoint.cp_message', + type: 'integer', + }, + 'checkpoint.log_delay': { + category: 'checkpoint', + description: 'Time left before deleting template. ', + name: 'checkpoint.log_delay', + type: 'integer', + }, + 'checkpoint.attack_status': { + category: 'checkpoint', + description: 'In case of a malicious event on an endpoint computer, the status of the attack. ', + name: 'checkpoint.attack_status', + type: 'keyword', + }, + 'checkpoint.impacted_files': { + category: 'checkpoint', + description: + 'In case of an infection on an endpoint computer, the list of files that the malware impacted. ', + name: 'checkpoint.impacted_files', + type: 'keyword', + }, + 'checkpoint.remediated_files': { + category: 'checkpoint', + description: + 'In case of an infection and a successful cleaning of that infection, this is a list of remediated files on the computer. ', + name: 'checkpoint.remediated_files', + type: 'keyword', + }, + 'checkpoint.triggered_by': { + category: 'checkpoint', + description: + 'The name of the mechanism that triggered the Software Blade to enforce a protection. ', + name: 'checkpoint.triggered_by', + type: 'keyword', + }, + 'checkpoint.https_inspection_rule_id': { + category: 'checkpoint', + description: 'ID of the matched rule. ', + name: 'checkpoint.https_inspection_rule_id', + type: 'keyword', + }, + 'checkpoint.https_inspection_rule_name': { + category: 'checkpoint', + description: 'Name of the matched rule. ', + name: 'checkpoint.https_inspection_rule_name', + type: 'keyword', + }, + 'checkpoint.app_properties': { + category: 'checkpoint', + description: 'List of all found categories. ', + name: 'checkpoint.app_properties', + type: 'keyword', + }, + 'checkpoint.https_validation': { + category: 'checkpoint', + description: 'Precise error, describing HTTPS inspection failure. ', + name: 'checkpoint.https_validation', + type: 'keyword', + }, + 'checkpoint.https_inspection_action': { + category: 'checkpoint', + description: 'HTTPS inspection action (Inspect/Bypass/Error). ', + name: 'checkpoint.https_inspection_action', + type: 'keyword', + }, + 'checkpoint.icap_service_id': { + category: 'checkpoint', + description: 'Service ID, can work with multiple servers, treated as services. ', + name: 'checkpoint.icap_service_id', + type: 'integer', + }, + 'checkpoint.icap_server_name': { + category: 'checkpoint', + description: 'Server name. ', + name: 'checkpoint.icap_server_name', + type: 'keyword', + }, + 'checkpoint.internal_error': { + category: 'checkpoint', + description: 'Internal error, for troubleshooting ', + name: 'checkpoint.internal_error', + type: 'keyword', + }, + 'checkpoint.icap_more_info': { + category: 'checkpoint', + description: 'Free text for verdict. ', + name: 'checkpoint.icap_more_info', + type: 'integer', + }, + 'checkpoint.reply_status': { + category: 'checkpoint', + description: 'ICAP reply status code, e.g. 200 or 204. ', + name: 'checkpoint.reply_status', + type: 'integer', + }, + 'checkpoint.icap_server_service': { + category: 'checkpoint', + description: 'Service name, as given in the ICAP URI ', + name: 'checkpoint.icap_server_service', + type: 'keyword', + }, + 'checkpoint.mirror_and_decrypt_type': { + category: 'checkpoint', + description: + 'Information about decrypt and forward. Possible values: Mirror only, Decrypt and mirror, Partial mirroring (HTTPS inspection Bypass). ', + name: 'checkpoint.mirror_and_decrypt_type', + type: 'keyword', + }, + 'checkpoint.interface_name': { + category: 'checkpoint', + description: 'Designated interface for mirror And decrypt. ', + name: 'checkpoint.interface_name', + type: 'keyword', + }, + 'checkpoint.session_uid': { + category: 'checkpoint', + description: 'HTTP session-id. ', + name: 'checkpoint.session_uid', + type: 'keyword', + }, + 'checkpoint.broker_publisher': { + category: 'checkpoint', + description: 'IP address of the broker publisher who shared the session information. ', + name: 'checkpoint.broker_publisher', + type: 'ip', + }, + 'checkpoint.src_user_dn': { + category: 'checkpoint', + description: 'User distinguished name connected to source IP. ', + name: 'checkpoint.src_user_dn', + type: 'keyword', + }, + 'checkpoint.proxy_user_name': { + category: 'checkpoint', + description: 'User name connected to proxy IP. ', + name: 'checkpoint.proxy_user_name', + type: 'keyword', + }, + 'checkpoint.proxy_machine_name': { + category: 'checkpoint', + description: 'Machine name connected to proxy IP. ', + name: 'checkpoint.proxy_machine_name', + type: 'integer', + }, + 'checkpoint.proxy_user_dn': { + category: 'checkpoint', + description: 'User distinguished name connected to proxy IP. ', + name: 'checkpoint.proxy_user_dn', + type: 'keyword', + }, + 'checkpoint.query': { + category: 'checkpoint', + description: 'DNS query. ', + name: 'checkpoint.query', + type: 'keyword', + }, + 'checkpoint.dns_query': { + category: 'checkpoint', + description: 'DNS query. ', + name: 'checkpoint.dns_query', + type: 'keyword', + }, + 'checkpoint.inspection_item': { + category: 'checkpoint', + description: 'Blade element performed inspection. ', + name: 'checkpoint.inspection_item', + type: 'keyword', + }, + 'checkpoint.inspection_category': { + category: 'checkpoint', + description: 'Inspection category: protocol anomaly, signature etc. ', + name: 'checkpoint.inspection_category', + type: 'keyword', + }, + 'checkpoint.inspection_profile': { + category: 'checkpoint', + description: 'Profile which the activated protection belongs to. ', + name: 'checkpoint.inspection_profile', + type: 'keyword', + }, + 'checkpoint.summary': { + category: 'checkpoint', + description: 'Summary message of a non-compliant DNS traffic drops or detects. ', + name: 'checkpoint.summary', + type: 'keyword', + }, + 'checkpoint.question_rdata': { + category: 'checkpoint', + description: 'List of question records domains. ', + name: 'checkpoint.question_rdata', + type: 'keyword', + }, + 'checkpoint.answer_rdata': { + category: 'checkpoint', + description: 'List of answer resource records to the questioned domains. ', + name: 'checkpoint.answer_rdata', + type: 'keyword', + }, + 'checkpoint.authority_rdata': { + category: 'checkpoint', + description: 'List of authoritative servers. ', + name: 'checkpoint.authority_rdata', + type: 'keyword', + }, + 'checkpoint.additional_rdata': { + category: 'checkpoint', + description: 'List of additional resource records. ', + name: 'checkpoint.additional_rdata', + type: 'keyword', + }, + 'checkpoint.files_names': { + category: 'checkpoint', + description: 'List of files requested by FTP. ', + name: 'checkpoint.files_names', + type: 'keyword', + }, + 'checkpoint.ftp_user': { + category: 'checkpoint', + description: 'FTP username. ', + name: 'checkpoint.ftp_user', + type: 'keyword', + }, + 'checkpoint.mime_from': { + category: 'checkpoint', + description: "Sender's address. ", + name: 'checkpoint.mime_from', + type: 'keyword', + }, + 'checkpoint.mime_to': { + category: 'checkpoint', + description: 'List of receiver address. ', + name: 'checkpoint.mime_to', + type: 'keyword', + }, + 'checkpoint.bcc': { + category: 'checkpoint', + description: 'List of BCC addresses. ', + name: 'checkpoint.bcc', + type: 'keyword', + }, + 'checkpoint.content_type': { + category: 'checkpoint', + description: + 'Mail content type. Possible values: application/msword, text/html, image/gif etc. ', + name: 'checkpoint.content_type', + type: 'keyword', + }, + 'checkpoint.user_agent': { + category: 'checkpoint', + description: 'String identifying requesting software user agent. ', + name: 'checkpoint.user_agent', + type: 'keyword', + }, + 'checkpoint.referrer': { + category: 'checkpoint', + description: 'Referrer HTTP request header, previous web page address. ', + name: 'checkpoint.referrer', + type: 'keyword', + }, + 'checkpoint.http_location': { + category: 'checkpoint', + description: 'Response header, indicates the URL to redirect a page to. ', + name: 'checkpoint.http_location', + type: 'keyword', + }, + 'checkpoint.content_disposition': { + category: 'checkpoint', + description: 'Indicates how the content is expected to be displayed inline in the browser. ', + name: 'checkpoint.content_disposition', + type: 'keyword', + }, + 'checkpoint.via': { + category: 'checkpoint', + description: + 'Via header is added by proxies for tracking purposes to avoid sending reqests in loop. ', + name: 'checkpoint.via', + type: 'keyword', + }, + 'checkpoint.http_server': { + category: 'checkpoint', + description: + 'Server HTTP header value, contains information about the software used by the origin server, which handles the request. ', + name: 'checkpoint.http_server', + type: 'keyword', + }, + 'checkpoint.content_length': { + category: 'checkpoint', + description: 'Indicates the size of the entity-body of the HTTP header. ', + name: 'checkpoint.content_length', + type: 'keyword', + }, + 'checkpoint.authorization': { + category: 'checkpoint', + description: 'Authorization HTTP header value. ', + name: 'checkpoint.authorization', + type: 'keyword', + }, + 'checkpoint.http_host': { + category: 'checkpoint', + description: 'Domain name of the server that the HTTP request is sent to. ', + name: 'checkpoint.http_host', + type: 'keyword', + }, + 'checkpoint.inspection_settings_log': { + category: 'checkpoint', + description: 'Indicats that the log was released by inspection settings. ', + name: 'checkpoint.inspection_settings_log', + type: 'keyword', + }, + 'checkpoint.cvpn_resource': { + category: 'checkpoint', + description: 'Mobile Access application. ', + name: 'checkpoint.cvpn_resource', + type: 'keyword', + }, + 'checkpoint.cvpn_category': { + category: 'checkpoint', + description: 'Mobile Access application type. ', + name: 'checkpoint.cvpn_category', + type: 'keyword', + }, + 'checkpoint.url': { + category: 'checkpoint', + description: 'Translated URL. ', + name: 'checkpoint.url', + type: 'keyword', + }, + 'checkpoint.reject_id': { + category: 'checkpoint', + description: + 'A reject ID that corresponds to the one presented in the Mobile Access error page. ', + name: 'checkpoint.reject_id', + type: 'keyword', + }, + 'checkpoint.fs-proto': { + category: 'checkpoint', + description: 'The file share protocol used in mobile acess file share application. ', + name: 'checkpoint.fs-proto', + type: 'keyword', + }, + 'checkpoint.app_package': { + category: 'checkpoint', + description: 'Unique identifier of the application on the protected mobile device. ', + name: 'checkpoint.app_package', + type: 'keyword', + }, + 'checkpoint.appi_name': { + category: 'checkpoint', + description: 'Name of application downloaded on the protected mobile device. ', + name: 'checkpoint.appi_name', + type: 'keyword', + }, + 'checkpoint.app_repackaged': { + category: 'checkpoint', + description: + 'Indicates whether the original application was repackage not by the official developer. ', + name: 'checkpoint.app_repackaged', + type: 'keyword', + }, + 'checkpoint.app_sid_id': { + category: 'checkpoint', + description: 'Unique SHA identifier of a mobile application. ', + name: 'checkpoint.app_sid_id', + type: 'keyword', + }, + 'checkpoint.app_version': { + category: 'checkpoint', + description: 'Version of the application downloaded on the protected mobile device. ', + name: 'checkpoint.app_version', + type: 'keyword', + }, + 'checkpoint.developer_certificate_name': { + category: 'checkpoint', + description: + "Name of the developer's certificate that was used to sign the mobile application. ", + name: 'checkpoint.developer_certificate_name', + type: 'keyword', + }, + 'checkpoint.email_message_id': { + category: 'checkpoint', + description: 'Email session id (uniqe ID of the mail). ', + name: 'checkpoint.email_message_id', + type: 'keyword', + }, + 'checkpoint.email_queue_id': { + category: 'checkpoint', + description: 'Postfix email queue id. ', + name: 'checkpoint.email_queue_id', + type: 'keyword', + }, + 'checkpoint.email_queue_name': { + category: 'checkpoint', + description: 'Postfix email queue name. ', + name: 'checkpoint.email_queue_name', + type: 'keyword', + }, + 'checkpoint.file_name': { + category: 'checkpoint', + description: 'Malicious file name. ', + name: 'checkpoint.file_name', + type: 'keyword', + }, + 'checkpoint.failure_reason': { + category: 'checkpoint', + description: 'MTA failure description. ', + name: 'checkpoint.failure_reason', + type: 'keyword', + }, + 'checkpoint.email_headers': { + category: 'checkpoint', + description: 'String containing all the email headers. ', + name: 'checkpoint.email_headers', + type: 'keyword', + }, + 'checkpoint.arrival_time': { + category: 'checkpoint', + description: 'Email arrival timestamp. ', + name: 'checkpoint.arrival_time', + type: 'keyword', + }, + 'checkpoint.email_status': { + category: 'checkpoint', + description: + "Describes the email's state. Possible options: delivered, deferred, skipped, bounced, hold, new, scan_started, scan_ended ", + name: 'checkpoint.email_status', + type: 'keyword', + }, + 'checkpoint.status_update': { + category: 'checkpoint', + description: 'Last time log was updated. ', + name: 'checkpoint.status_update', + type: 'keyword', + }, + 'checkpoint.delivery_time': { + category: 'checkpoint', + description: 'Timestamp of when email was delivered (MTA finished handling the email. ', + name: 'checkpoint.delivery_time', + type: 'keyword', + }, + 'checkpoint.links_num': { + category: 'checkpoint', + description: 'Number of links in the mail. ', + name: 'checkpoint.links_num', + type: 'integer', + }, + 'checkpoint.attachments_num': { + category: 'checkpoint', + description: 'Number of attachments in the mail. ', + name: 'checkpoint.attachments_num', + type: 'integer', + }, + 'checkpoint.email_content': { + category: 'checkpoint', + description: + 'Mail contents. Possible options: attachments/links & attachments/links/text only. ', + name: 'checkpoint.email_content', + type: 'keyword', + }, + 'checkpoint.allocated_ports': { + category: 'checkpoint', + description: 'Amount of allocated ports. ', + name: 'checkpoint.allocated_ports', + type: 'integer', + }, + 'checkpoint.capacity': { + category: 'checkpoint', + description: 'Capacity of the ports. ', + name: 'checkpoint.capacity', + type: 'integer', + }, + 'checkpoint.ports_usage': { + category: 'checkpoint', + description: 'Percentage of allocated ports. ', + name: 'checkpoint.ports_usage', + type: 'integer', + }, + 'checkpoint.nat_exhausted_pool': { + category: 'checkpoint', + description: '4-tuple of an exhausted pool. ', + name: 'checkpoint.nat_exhausted_pool', + type: 'keyword', + }, + 'checkpoint.nat_rulenum': { + category: 'checkpoint', + description: 'NAT rulebase first matched rule. ', + name: 'checkpoint.nat_rulenum', + type: 'integer', + }, + 'checkpoint.nat_addtnl_rulenum': { + category: 'checkpoint', + description: + 'When matching 2 automatic rules , second rule match will be shown otherwise field will be 0. ', + name: 'checkpoint.nat_addtnl_rulenum', + type: 'integer', + }, + 'checkpoint.message_info': { + category: 'checkpoint', + description: 'Used for information messages, for example:NAT connection has ended. ', + name: 'checkpoint.message_info', + type: 'keyword', + }, + 'checkpoint.nat46': { + category: 'checkpoint', + description: 'NAT 46 status, in most cases "enabled". ', + name: 'checkpoint.nat46', + type: 'keyword', + }, + 'checkpoint.end_time': { + category: 'checkpoint', + description: 'TCP connection end time. ', + name: 'checkpoint.end_time', + type: 'keyword', + }, + 'checkpoint.tcp_end_reason': { + category: 'checkpoint', + description: 'Reason for TCP connection closure. ', + name: 'checkpoint.tcp_end_reason', + type: 'keyword', + }, + 'checkpoint.cgnet': { + category: 'checkpoint', + description: 'Describes NAT allocation for specific subscriber. ', + name: 'checkpoint.cgnet', + type: 'keyword', + }, + 'checkpoint.subscriber': { + category: 'checkpoint', + description: 'Source IP before CGNAT. ', + name: 'checkpoint.subscriber', + type: 'ip', + }, + 'checkpoint.hide_ip': { + category: 'checkpoint', + description: 'Source IP which will be used after CGNAT. ', + name: 'checkpoint.hide_ip', + type: 'ip', + }, + 'checkpoint.int_start': { + category: 'checkpoint', + description: 'Subscriber start int which will be used for NAT. ', + name: 'checkpoint.int_start', + type: 'integer', + }, + 'checkpoint.int_end': { + category: 'checkpoint', + description: 'Subscriber end int which will be used for NAT. ', + name: 'checkpoint.int_end', + type: 'integer', + }, + 'checkpoint.packet_amount': { + category: 'checkpoint', + description: 'Amount of packets dropped. ', + name: 'checkpoint.packet_amount', + type: 'integer', + }, + 'checkpoint.monitor_reason': { + category: 'checkpoint', + description: 'Aggregated logs of monitored packets. ', + name: 'checkpoint.monitor_reason', + type: 'keyword', + }, + 'checkpoint.drops_amount': { + category: 'checkpoint', + description: 'Amount of multicast packets dropped. ', + name: 'checkpoint.drops_amount', + type: 'integer', + }, + 'checkpoint.securexl_message': { + category: 'checkpoint', + description: + 'Two options for a SecureXL message: 1. Missed accounting records after heavy load on logging system. 2. FW log message regarding a packet drop. ', + name: 'checkpoint.securexl_message', + type: 'keyword', + }, + 'checkpoint.conns_amount': { + category: 'checkpoint', + description: 'Connections amount of aggregated log info. ', + name: 'checkpoint.conns_amount', + type: 'integer', + }, + 'checkpoint.scope': { + category: 'checkpoint', + description: 'IP related to the attack. ', + name: 'checkpoint.scope', + type: 'keyword', + }, + 'checkpoint.analyzed_on': { + category: 'checkpoint', + description: 'Check Point ThreatCloud / emulator name. ', + name: 'checkpoint.analyzed_on', + type: 'keyword', + }, + 'checkpoint.detected_on': { + category: 'checkpoint', + description: 'System and applications version the file was emulated on. ', + name: 'checkpoint.detected_on', + type: 'keyword', + }, + 'checkpoint.dropped_file_name': { + category: 'checkpoint', + description: 'List of names dropped from the original file. ', + name: 'checkpoint.dropped_file_name', + type: 'keyword', + }, + 'checkpoint.dropped_file_type': { + category: 'checkpoint', + description: 'List of file types dropped from the original file. ', + name: 'checkpoint.dropped_file_type', + type: 'keyword', + }, + 'checkpoint.dropped_file_hash': { + category: 'checkpoint', + description: 'List of file hashes dropped from the original file. ', + name: 'checkpoint.dropped_file_hash', + type: 'keyword', + }, + 'checkpoint.dropped_file_verdict': { + category: 'checkpoint', + description: 'List of file verdics dropped from the original file. ', + name: 'checkpoint.dropped_file_verdict', + type: 'keyword', + }, + 'checkpoint.emulated_on': { + category: 'checkpoint', + description: 'Images the files were emulated on. ', + name: 'checkpoint.emulated_on', + type: 'keyword', + }, + 'checkpoint.extracted_file_type': { + category: 'checkpoint', + description: 'Types of extracted files in case of an archive. ', + name: 'checkpoint.extracted_file_type', + type: 'keyword', + }, + 'checkpoint.extracted_file_names': { + category: 'checkpoint', + description: 'Names of extracted files in case of an archive. ', + name: 'checkpoint.extracted_file_names', + type: 'keyword', + }, + 'checkpoint.extracted_file_hash': { + category: 'checkpoint', + description: 'Archive hash in case of extracted files. ', + name: 'checkpoint.extracted_file_hash', + type: 'keyword', + }, + 'checkpoint.extracted_file_verdict': { + category: 'checkpoint', + description: 'Verdict of extracted files in case of an archive. ', + name: 'checkpoint.extracted_file_verdict', + type: 'keyword', + }, + 'checkpoint.extracted_file_uid': { + category: 'checkpoint', + description: 'UID of extracted files in case of an archive. ', + name: 'checkpoint.extracted_file_uid', + type: 'keyword', + }, + 'checkpoint.mitre_initial_access': { + category: 'checkpoint', + description: 'The adversary is trying to break into your network. ', + name: 'checkpoint.mitre_initial_access', + type: 'keyword', + }, + 'checkpoint.mitre_execution': { + category: 'checkpoint', + description: 'The adversary is trying to run malicious code. ', + name: 'checkpoint.mitre_execution', + type: 'keyword', + }, + 'checkpoint.mitre_persistence': { + category: 'checkpoint', + description: 'The adversary is trying to maintain his foothold. ', + name: 'checkpoint.mitre_persistence', + type: 'keyword', + }, + 'checkpoint.mitre_privilege_escalation': { + category: 'checkpoint', + description: 'The adversary is trying to gain higher-level permissions. ', + name: 'checkpoint.mitre_privilege_escalation', + type: 'keyword', + }, + 'checkpoint.mitre_defense_evasion': { + category: 'checkpoint', + description: 'The adversary is trying to avoid being detected. ', + name: 'checkpoint.mitre_defense_evasion', + type: 'keyword', + }, + 'checkpoint.mitre_credential_access': { + category: 'checkpoint', + description: 'The adversary is trying to steal account names and passwords. ', + name: 'checkpoint.mitre_credential_access', + type: 'keyword', + }, + 'checkpoint.mitre_discovery': { + category: 'checkpoint', + description: 'The adversary is trying to expose information about your environment. ', + name: 'checkpoint.mitre_discovery', + type: 'keyword', + }, + 'checkpoint.mitre_lateral_movement': { + category: 'checkpoint', + description: 'The adversary is trying to explore your environment. ', + name: 'checkpoint.mitre_lateral_movement', + type: 'keyword', + }, + 'checkpoint.mitre_collection': { + category: 'checkpoint', + description: 'The adversary is trying to collect data of interest to achieve his goal. ', + name: 'checkpoint.mitre_collection', + type: 'keyword', + }, + 'checkpoint.mitre_command_and_control': { + category: 'checkpoint', + description: + 'The adversary is trying to communicate with compromised systems in order to control them. ', + name: 'checkpoint.mitre_command_and_control', + type: 'keyword', + }, + 'checkpoint.mitre_exfiltration': { + category: 'checkpoint', + description: 'The adversary is trying to steal data. ', + name: 'checkpoint.mitre_exfiltration', + type: 'keyword', + }, + 'checkpoint.mitre_impact': { + category: 'checkpoint', + description: + 'The adversary is trying to manipulate, interrupt, or destroy your systems and data. ', + name: 'checkpoint.mitre_impact', + type: 'keyword', + }, + 'checkpoint.parent_file_hash': { + category: 'checkpoint', + description: "Archive's hash in case of extracted files. ", + name: 'checkpoint.parent_file_hash', + type: 'keyword', + }, + 'checkpoint.parent_file_name': { + category: 'checkpoint', + description: "Archive's name in case of extracted files. ", + name: 'checkpoint.parent_file_name', + type: 'keyword', + }, + 'checkpoint.parent_file_uid': { + category: 'checkpoint', + description: "Archive's UID in case of extracted files. ", + name: 'checkpoint.parent_file_uid', + type: 'keyword', + }, + 'checkpoint.similiar_iocs': { + category: 'checkpoint', + description: 'Other IoCs similar to the ones found, related to the malicious file. ', + name: 'checkpoint.similiar_iocs', + type: 'keyword', + }, + 'checkpoint.similar_hashes': { + category: 'checkpoint', + description: 'Hashes found similar to the malicious file. ', + name: 'checkpoint.similar_hashes', + type: 'keyword', + }, + 'checkpoint.similar_strings': { + category: 'checkpoint', + description: 'Strings found similar to the malicious file. ', + name: 'checkpoint.similar_strings', + type: 'keyword', + }, + 'checkpoint.similar_communication': { + category: 'checkpoint', + description: 'Network action found similar to the malicious file. ', + name: 'checkpoint.similar_communication', + type: 'keyword', + }, + 'checkpoint.te_verdict_determined_by': { + category: 'checkpoint', + description: 'Emulators determined file verdict. ', + name: 'checkpoint.te_verdict_determined_by', + type: 'keyword', + }, + 'checkpoint.packet_capture_unique_id': { + category: 'checkpoint', + description: 'Identifier of the packet capture files. ', + name: 'checkpoint.packet_capture_unique_id', + type: 'keyword', + }, + 'checkpoint.total_attachments': { + category: 'checkpoint', + description: 'The number of attachments in an email. ', + name: 'checkpoint.total_attachments', + type: 'integer', + }, + 'checkpoint.additional_info': { + category: 'checkpoint', + description: 'ID of original file/mail which are sent by admin. ', + name: 'checkpoint.additional_info', + type: 'keyword', + }, + 'checkpoint.content_risk': { + category: 'checkpoint', + description: 'File risk. ', + name: 'checkpoint.content_risk', + type: 'integer', + }, + 'checkpoint.operation': { + category: 'checkpoint', + description: 'Operation made by Threat Extraction. ', + name: 'checkpoint.operation', + type: 'keyword', + }, + 'checkpoint.scrubbed_content': { + category: 'checkpoint', + description: 'Active content that was found. ', + name: 'checkpoint.scrubbed_content', + type: 'keyword', + }, + 'checkpoint.scrub_time': { + category: 'checkpoint', + description: 'Extraction process duration. ', + name: 'checkpoint.scrub_time', + type: 'keyword', + }, + 'checkpoint.scrub_download_time': { + category: 'checkpoint', + description: 'File download time from resource. ', + name: 'checkpoint.scrub_download_time', + type: 'keyword', + }, + 'checkpoint.scrub_total_time': { + category: 'checkpoint', + description: 'Threat extraction total file handling time. ', + name: 'checkpoint.scrub_total_time', + type: 'keyword', + }, + 'checkpoint.scrub_activity': { + category: 'checkpoint', + description: 'The result of the extraction ', + name: 'checkpoint.scrub_activity', + type: 'keyword', + }, + 'checkpoint.watermark': { + category: 'checkpoint', + description: 'Reports whether watermark is added to the cleaned file. ', + name: 'checkpoint.watermark', + type: 'keyword', + }, + 'checkpoint.source_object': { + category: 'checkpoint', + description: 'Matched object name on source column. ', + name: 'checkpoint.source_object', + type: 'integer', + }, + 'checkpoint.destination_object': { + category: 'checkpoint', + description: 'Matched object name on destination column. ', + name: 'checkpoint.destination_object', + type: 'keyword', + }, + 'checkpoint.drop_reason': { + category: 'checkpoint', + description: 'Drop reason description. ', + name: 'checkpoint.drop_reason', + type: 'keyword', + }, + 'checkpoint.hit': { + category: 'checkpoint', + description: 'Number of hits on a rule. ', + name: 'checkpoint.hit', + type: 'integer', + }, + 'checkpoint.rulebase_id': { + category: 'checkpoint', + description: 'Layer number. ', + name: 'checkpoint.rulebase_id', + type: 'integer', + }, + 'checkpoint.first_hit_time': { + category: 'checkpoint', + description: 'First hit time in current interval. ', + name: 'checkpoint.first_hit_time', + type: 'integer', + }, + 'checkpoint.last_hit_time': { + category: 'checkpoint', + description: 'Last hit time in current interval. ', + name: 'checkpoint.last_hit_time', + type: 'integer', + }, + 'checkpoint.rematch_info': { + category: 'checkpoint', + description: + 'Information sent when old connections cannot be matched during policy installation. ', + name: 'checkpoint.rematch_info', + type: 'keyword', + }, + 'checkpoint.last_rematch_time': { + category: 'checkpoint', + description: 'Connection rematched time. ', + name: 'checkpoint.last_rematch_time', + type: 'keyword', + }, + 'checkpoint.action_reason': { + category: 'checkpoint', + description: 'Connection drop reason. ', + name: 'checkpoint.action_reason', + type: 'integer', + }, + 'checkpoint.c_bytes': { + category: 'checkpoint', + description: 'Boolean value indicates whether bytes sent from the client side are used. ', + name: 'checkpoint.c_bytes', + type: 'integer', + }, + 'checkpoint.context_num': { + category: 'checkpoint', + description: 'Serial number of the log for a specific connection. ', + name: 'checkpoint.context_num', + type: 'integer', + }, + 'checkpoint.match_id': { + category: 'checkpoint', + description: 'Private key of the rule ', + name: 'checkpoint.match_id', + type: 'integer', + }, + 'checkpoint.alert': { + category: 'checkpoint', + description: 'Alert level of matched rule (for connection logs). ', + name: 'checkpoint.alert', + type: 'keyword', + }, + 'checkpoint.parent_rule': { + category: 'checkpoint', + description: 'Parent rule number, in case of inline layer. ', + name: 'checkpoint.parent_rule', + type: 'integer', + }, + 'checkpoint.match_fk': { + category: 'checkpoint', + description: 'Rule number. ', + name: 'checkpoint.match_fk', + type: 'integer', + }, + 'checkpoint.dropped_outgoing': { + category: 'checkpoint', + description: 'Number of outgoing bytes dropped when using UP-limit feature. ', + name: 'checkpoint.dropped_outgoing', + type: 'integer', + }, + 'checkpoint.dropped_incoming': { + category: 'checkpoint', + description: 'Number of incoming bytes dropped when using UP-limit feature. ', + name: 'checkpoint.dropped_incoming', + type: 'integer', + }, + 'checkpoint.media_type': { + category: 'checkpoint', + description: 'Media used (audio, video, etc.) ', + name: 'checkpoint.media_type', + type: 'keyword', + }, + 'checkpoint.sip_reason': { + category: 'checkpoint', + description: "Explains why 'source_ip' isn't allowed to redirect (handover). ", + name: 'checkpoint.sip_reason', + type: 'keyword', + }, + 'checkpoint.voip_method': { + category: 'checkpoint', + description: 'Registration request. ', + name: 'checkpoint.voip_method', + type: 'keyword', + }, + 'checkpoint.registered_ip-phones': { + category: 'checkpoint', + description: 'Registered IP-Phones. ', + name: 'checkpoint.registered_ip-phones', + type: 'keyword', + }, + 'checkpoint.voip_reg_user_type': { + category: 'checkpoint', + description: 'Registered IP-Phone type. ', + name: 'checkpoint.voip_reg_user_type', + type: 'keyword', + }, + 'checkpoint.voip_call_id': { + category: 'checkpoint', + description: 'Call-ID. ', + name: 'checkpoint.voip_call_id', + type: 'keyword', + }, + 'checkpoint.voip_reg_int': { + category: 'checkpoint', + description: 'Registration port. ', + name: 'checkpoint.voip_reg_int', + type: 'integer', + }, + 'checkpoint.voip_reg_ipp': { + category: 'checkpoint', + description: 'Registration IP protocol. ', + name: 'checkpoint.voip_reg_ipp', + type: 'integer', + }, + 'checkpoint.voip_reg_period': { + category: 'checkpoint', + description: 'Registration period. ', + name: 'checkpoint.voip_reg_period', + type: 'integer', + }, + 'checkpoint.src_phone_number': { + category: 'checkpoint', + description: 'Source IP-Phone. ', + name: 'checkpoint.src_phone_number', + type: 'keyword', + }, + 'checkpoint.voip_from_user_type': { + category: 'checkpoint', + description: 'Source IP-Phone type. ', + name: 'checkpoint.voip_from_user_type', + type: 'keyword', + }, + 'checkpoint.voip_to_user_type': { + category: 'checkpoint', + description: 'Destination IP-Phone type. ', + name: 'checkpoint.voip_to_user_type', + type: 'keyword', + }, + 'checkpoint.voip_call_dir': { + category: 'checkpoint', + description: 'Call direction: in/out. ', + name: 'checkpoint.voip_call_dir', + type: 'keyword', + }, + 'checkpoint.voip_call_state': { + category: 'checkpoint', + description: 'Call state. Possible values: in/out. ', + name: 'checkpoint.voip_call_state', + type: 'keyword', + }, + 'checkpoint.voip_call_term_time': { + category: 'checkpoint', + description: 'Call termination time stamp. ', + name: 'checkpoint.voip_call_term_time', + type: 'keyword', + }, + 'checkpoint.voip_duration': { + category: 'checkpoint', + description: 'Call duration (seconds). ', + name: 'checkpoint.voip_duration', + type: 'keyword', + }, + 'checkpoint.voip_media_port': { + category: 'checkpoint', + description: 'Media int. ', + name: 'checkpoint.voip_media_port', + type: 'keyword', + }, + 'checkpoint.voip_media_ipp': { + category: 'checkpoint', + description: 'Media IP protocol. ', + name: 'checkpoint.voip_media_ipp', + type: 'keyword', + }, + 'checkpoint.voip_est_codec': { + category: 'checkpoint', + description: 'Estimated codec. ', + name: 'checkpoint.voip_est_codec', + type: 'keyword', + }, + 'checkpoint.voip_exp': { + category: 'checkpoint', + description: 'Expiration. ', + name: 'checkpoint.voip_exp', + type: 'integer', + }, + 'checkpoint.voip_attach_sz': { + category: 'checkpoint', + description: 'Attachment size. ', + name: 'checkpoint.voip_attach_sz', + type: 'integer', + }, + 'checkpoint.voip_attach_action_info': { + category: 'checkpoint', + description: 'Attachment action Info. ', + name: 'checkpoint.voip_attach_action_info', + type: 'keyword', + }, + 'checkpoint.voip_media_codec': { + category: 'checkpoint', + description: 'Estimated codec. ', + name: 'checkpoint.voip_media_codec', + type: 'keyword', + }, + 'checkpoint.voip_reject_reason': { + category: 'checkpoint', + description: 'Reject reason. ', + name: 'checkpoint.voip_reject_reason', + type: 'keyword', + }, + 'checkpoint.voip_reason_info': { + category: 'checkpoint', + description: 'Information. ', + name: 'checkpoint.voip_reason_info', + type: 'keyword', + }, + 'checkpoint.voip_config': { + category: 'checkpoint', + description: 'Configuration. ', + name: 'checkpoint.voip_config', + type: 'keyword', + }, + 'checkpoint.voip_reg_server': { + category: 'checkpoint', + description: 'Registrar server IP address. ', + name: 'checkpoint.voip_reg_server', + type: 'ip', + }, + 'checkpoint.scv_user': { + category: 'checkpoint', + description: 'Username whose packets are dropped on SCV. ', + name: 'checkpoint.scv_user', + type: 'keyword', + }, + 'checkpoint.scv_message_info': { + category: 'checkpoint', + description: 'Drop reason. ', + name: 'checkpoint.scv_message_info', + type: 'keyword', + }, + 'checkpoint.ppp': { + category: 'checkpoint', + description: 'Authentication status. ', + name: 'checkpoint.ppp', + type: 'keyword', + }, + 'checkpoint.scheme': { + category: 'checkpoint', + description: 'Describes the scheme used for the log. ', + name: 'checkpoint.scheme', + type: 'keyword', + }, + 'checkpoint.machine': { + category: 'checkpoint', + description: 'L2TP machine which triggered the log and the log refers to it. ', + name: 'checkpoint.machine', + type: 'keyword', + }, + 'checkpoint.vpn_feature_name': { + category: 'checkpoint', + description: 'L2TP /IKE / Link Selection. ', + name: 'checkpoint.vpn_feature_name', + type: 'keyword', + }, + 'checkpoint.reject_category': { + category: 'checkpoint', + description: 'Authentication failure reason. ', + name: 'checkpoint.reject_category', + type: 'keyword', + }, + 'checkpoint.peer_ip_probing_status_update': { + category: 'checkpoint', + description: 'IP address response status. ', + name: 'checkpoint.peer_ip_probing_status_update', + type: 'keyword', + }, + 'checkpoint.peer_ip': { + category: 'checkpoint', + description: 'IP address which the client connects to. ', + name: 'checkpoint.peer_ip', + type: 'keyword', + }, + 'checkpoint.link_probing_status_update': { + category: 'checkpoint', + description: 'IP address response status. ', + name: 'checkpoint.link_probing_status_update', + type: 'keyword', + }, + 'checkpoint.source_interface': { + category: 'checkpoint', + description: 'External Interface name for source interface or Null if not found. ', + name: 'checkpoint.source_interface', + type: 'keyword', + }, + 'checkpoint.next_hop_ip': { + category: 'checkpoint', + description: 'Next hop IP address. ', + name: 'checkpoint.next_hop_ip', + type: 'keyword', + }, + 'checkpoint.srckeyid': { + category: 'checkpoint', + description: 'Initiator Spi ID. ', + name: 'checkpoint.srckeyid', + type: 'keyword', + }, + 'checkpoint.dstkeyid': { + category: 'checkpoint', + description: 'Responder Spi ID. ', + name: 'checkpoint.dstkeyid', + type: 'keyword', + }, + 'checkpoint.encryption_failure': { + category: 'checkpoint', + description: 'Message indicating why the encryption failed. ', + name: 'checkpoint.encryption_failure', + type: 'keyword', + }, + 'checkpoint.ike_ids': { + category: 'checkpoint', + description: 'All QM ids. ', + name: 'checkpoint.ike_ids', + type: 'keyword', + }, + 'checkpoint.community': { + category: 'checkpoint', + description: 'Community name for the IPSec key and the use of the IKEv. ', + name: 'checkpoint.community', + type: 'keyword', + }, + 'checkpoint.ike': { + category: 'checkpoint', + description: 'IKEMode (PHASE1, PHASE2, etc..). ', + name: 'checkpoint.ike', + type: 'keyword', + }, + 'checkpoint.cookieI': { + category: 'checkpoint', + description: 'Initiator cookie. ', + name: 'checkpoint.cookieI', + type: 'keyword', + }, + 'checkpoint.cookieR': { + category: 'checkpoint', + description: 'Responder cookie. ', + name: 'checkpoint.cookieR', + type: 'keyword', + }, + 'checkpoint.msgid': { + category: 'checkpoint', + description: 'Message ID. ', + name: 'checkpoint.msgid', + type: 'keyword', + }, + 'checkpoint.methods': { + category: 'checkpoint', + description: 'IPSEc methods. ', + name: 'checkpoint.methods', + type: 'keyword', + }, + 'checkpoint.connection_uid': { + category: 'checkpoint', + description: 'Calculation of md5 of the IP and user name as UID. ', + name: 'checkpoint.connection_uid', + type: 'keyword', + }, + 'checkpoint.site_name': { + category: 'checkpoint', + description: 'Site name. ', + name: 'checkpoint.site_name', + type: 'keyword', + }, + 'checkpoint.esod_rule_name': { + category: 'checkpoint', + description: 'Unknown rule name. ', + name: 'checkpoint.esod_rule_name', + type: 'keyword', + }, + 'checkpoint.esod_rule_action': { + category: 'checkpoint', + description: 'Unknown rule action. ', + name: 'checkpoint.esod_rule_action', + type: 'keyword', + }, + 'checkpoint.esod_rule_type': { + category: 'checkpoint', + description: 'Unknown rule type. ', + name: 'checkpoint.esod_rule_type', + type: 'keyword', + }, + 'checkpoint.esod_noncompliance_reason': { + category: 'checkpoint', + description: 'Non-compliance reason. ', + name: 'checkpoint.esod_noncompliance_reason', + type: 'keyword', + }, + 'checkpoint.esod_associated_policies': { + category: 'checkpoint', + description: 'Associated policies. ', + name: 'checkpoint.esod_associated_policies', + type: 'keyword', + }, + 'checkpoint.spyware_type': { + category: 'checkpoint', + description: 'Spyware type. ', + name: 'checkpoint.spyware_type', + type: 'keyword', + }, + 'checkpoint.anti_virus_type': { + category: 'checkpoint', + description: 'Anti virus type. ', + name: 'checkpoint.anti_virus_type', + type: 'keyword', + }, + 'checkpoint.end_user_firewall_type': { + category: 'checkpoint', + description: 'End user firewall type. ', + name: 'checkpoint.end_user_firewall_type', + type: 'keyword', + }, + 'checkpoint.esod_scan_status': { + category: 'checkpoint', + description: 'Scan failed. ', + name: 'checkpoint.esod_scan_status', + type: 'keyword', + }, + 'checkpoint.esod_access_status': { + category: 'checkpoint', + description: 'Access denied. ', + name: 'checkpoint.esod_access_status', + type: 'keyword', + }, + 'checkpoint.client_type': { + category: 'checkpoint', + description: 'Endpoint Connect. ', + name: 'checkpoint.client_type', + type: 'keyword', + }, + 'checkpoint.precise_error': { + category: 'checkpoint', + description: 'HTTP parser error. ', + name: 'checkpoint.precise_error', + type: 'keyword', + }, + 'checkpoint.method': { + category: 'checkpoint', + description: 'HTTP method. ', + name: 'checkpoint.method', + type: 'keyword', + }, + 'checkpoint.trusted_domain': { + category: 'checkpoint', + description: 'In case of phishing event, the domain, which the attacker was impersonating. ', + name: 'checkpoint.trusted_domain', + type: 'keyword', + }, + 'cisco.asa.message_id': { + category: 'cisco', + description: 'The Cisco ASA message identifier. ', + name: 'cisco.asa.message_id', + type: 'keyword', + }, + 'cisco.asa.suffix': { + category: 'cisco', + description: 'Optional suffix after %ASA identifier. ', + example: 'session', + name: 'cisco.asa.suffix', + type: 'keyword', + }, + 'cisco.asa.source_interface': { + category: 'cisco', + description: 'Source interface for the flow or event. ', + name: 'cisco.asa.source_interface', + type: 'keyword', + }, + 'cisco.asa.destination_interface': { + category: 'cisco', + description: 'Destination interface for the flow or event. ', + name: 'cisco.asa.destination_interface', + type: 'keyword', + }, + 'cisco.asa.rule_name': { + category: 'cisco', + description: 'Name of the Access Control List rule that matched this event. ', + name: 'cisco.asa.rule_name', + type: 'keyword', + }, + 'cisco.asa.source_username': { + category: 'cisco', + description: 'Name of the user that is the source for this event. ', + name: 'cisco.asa.source_username', + type: 'keyword', + }, + 'cisco.asa.destination_username': { + category: 'cisco', + description: 'Name of the user that is the destination for this event. ', + name: 'cisco.asa.destination_username', + type: 'keyword', + }, + 'cisco.asa.mapped_source_ip': { + category: 'cisco', + description: 'The translated source IP address. ', + name: 'cisco.asa.mapped_source_ip', + type: 'ip', + }, + 'cisco.asa.mapped_source_host': { + category: 'cisco', + description: 'The translated source host. ', + name: 'cisco.asa.mapped_source_host', + type: 'keyword', + }, + 'cisco.asa.mapped_source_port': { + category: 'cisco', + description: 'The translated source port. ', + name: 'cisco.asa.mapped_source_port', + type: 'long', + }, + 'cisco.asa.mapped_destination_ip': { + category: 'cisco', + description: 'The translated destination IP address. ', + name: 'cisco.asa.mapped_destination_ip', + type: 'ip', + }, + 'cisco.asa.mapped_destination_host': { + category: 'cisco', + description: 'The translated destination host. ', + name: 'cisco.asa.mapped_destination_host', + type: 'keyword', + }, + 'cisco.asa.mapped_destination_port': { + category: 'cisco', + description: 'The translated destination port. ', + name: 'cisco.asa.mapped_destination_port', + type: 'long', + }, + 'cisco.asa.threat_level': { + category: 'cisco', + description: + 'Threat level for malware / botnet traffic. One of very-low, low, moderate, high or very-high. ', + name: 'cisco.asa.threat_level', + type: 'keyword', + }, + 'cisco.asa.threat_category': { + category: 'cisco', + description: + 'Category for the malware / botnet traffic. For example: virus, botnet, trojan, etc. ', + name: 'cisco.asa.threat_category', + type: 'keyword', + }, + 'cisco.asa.connection_id': { + category: 'cisco', + description: 'Unique identifier for a flow. ', + name: 'cisco.asa.connection_id', + type: 'keyword', + }, + 'cisco.asa.icmp_type': { + category: 'cisco', + description: 'ICMP type. ', + name: 'cisco.asa.icmp_type', + type: 'short', + }, + 'cisco.asa.icmp_code': { + category: 'cisco', + description: 'ICMP code. ', + name: 'cisco.asa.icmp_code', + type: 'short', + }, + 'cisco.asa.connection_type': { + category: 'cisco', + description: 'The VPN connection type ', + name: 'cisco.asa.connection_type', + type: 'keyword', + }, + 'cisco.asa.dap_records': { + category: 'cisco', + description: 'The assigned DAP records ', + name: 'cisco.asa.dap_records', + type: 'keyword', + }, + 'cisco.ftd.message_id': { + category: 'cisco', + description: 'The Cisco FTD message identifier. ', + name: 'cisco.ftd.message_id', + type: 'keyword', + }, + 'cisco.ftd.suffix': { + category: 'cisco', + description: 'Optional suffix after %FTD identifier. ', + example: 'session', + name: 'cisco.ftd.suffix', + type: 'keyword', + }, + 'cisco.ftd.source_interface': { + category: 'cisco', + description: 'Source interface for the flow or event. ', + name: 'cisco.ftd.source_interface', + type: 'keyword', + }, + 'cisco.ftd.destination_interface': { + category: 'cisco', + description: 'Destination interface for the flow or event. ', + name: 'cisco.ftd.destination_interface', + type: 'keyword', + }, + 'cisco.ftd.rule_name': { + category: 'cisco', + description: 'Name of the Access Control List rule that matched this event. ', + name: 'cisco.ftd.rule_name', + type: 'keyword', + }, + 'cisco.ftd.source_username': { + category: 'cisco', + description: 'Name of the user that is the source for this event. ', + name: 'cisco.ftd.source_username', + type: 'keyword', + }, + 'cisco.ftd.destination_username': { + category: 'cisco', + description: 'Name of the user that is the destination for this event. ', + name: 'cisco.ftd.destination_username', + type: 'keyword', + }, + 'cisco.ftd.mapped_source_ip': { + category: 'cisco', + description: 'The translated source IP address. Use ECS source.nat.ip. ', + name: 'cisco.ftd.mapped_source_ip', + type: 'ip', + }, + 'cisco.ftd.mapped_source_host': { + category: 'cisco', + description: 'The translated source host. ', + name: 'cisco.ftd.mapped_source_host', + type: 'keyword', + }, + 'cisco.ftd.mapped_source_port': { + category: 'cisco', + description: 'The translated source port. Use ECS source.nat.port. ', + name: 'cisco.ftd.mapped_source_port', + type: 'long', + }, + 'cisco.ftd.mapped_destination_ip': { + category: 'cisco', + description: 'The translated destination IP address. Use ECS destination.nat.ip. ', + name: 'cisco.ftd.mapped_destination_ip', + type: 'ip', + }, + 'cisco.ftd.mapped_destination_host': { + category: 'cisco', + description: 'The translated destination host. ', + name: 'cisco.ftd.mapped_destination_host', + type: 'keyword', + }, + 'cisco.ftd.mapped_destination_port': { + category: 'cisco', + description: 'The translated destination port. Use ECS destination.nat.port. ', + name: 'cisco.ftd.mapped_destination_port', + type: 'long', + }, + 'cisco.ftd.threat_level': { + category: 'cisco', + description: + 'Threat level for malware / botnet traffic. One of very-low, low, moderate, high or very-high. ', + name: 'cisco.ftd.threat_level', + type: 'keyword', + }, + 'cisco.ftd.threat_category': { + category: 'cisco', + description: + 'Category for the malware / botnet traffic. For example: virus, botnet, trojan, etc. ', + name: 'cisco.ftd.threat_category', + type: 'keyword', + }, + 'cisco.ftd.connection_id': { + category: 'cisco', + description: 'Unique identifier for a flow. ', + name: 'cisco.ftd.connection_id', + type: 'keyword', + }, + 'cisco.ftd.icmp_type': { + category: 'cisco', + description: 'ICMP type. ', + name: 'cisco.ftd.icmp_type', + type: 'short', + }, + 'cisco.ftd.icmp_code': { + category: 'cisco', + description: 'ICMP code. ', + name: 'cisco.ftd.icmp_code', + type: 'short', + }, + 'cisco.ftd.security': { + category: 'cisco', + description: 'Raw fields for Security Events.', + name: 'cisco.ftd.security', + type: 'object', + }, + 'cisco.ftd.connection_type': { + category: 'cisco', + description: 'The VPN connection type ', + name: 'cisco.ftd.connection_type', + type: 'keyword', + }, + 'cisco.ftd.dap_records': { + category: 'cisco', + description: 'The assigned DAP records ', + name: 'cisco.ftd.dap_records', + type: 'keyword', + }, + 'cisco.ios.access_list': { + category: 'cisco', + description: 'Name of the IP access list. ', + name: 'cisco.ios.access_list', + type: 'keyword', + }, + 'cisco.ios.facility': { + category: 'cisco', + description: + 'The facility to which the message refers (for example, SNMP, SYS, and so forth). A facility can be a hardware device, a protocol, or a module of the system software. It denotes the source or the cause of the system message. ', + example: 'SEC', + name: 'cisco.ios.facility', + type: 'keyword', + }, + 'coredns.id': { + category: 'coredns', + description: 'id of the DNS transaction ', + name: 'coredns.id', + type: 'keyword', + }, + 'coredns.query.size': { + category: 'coredns', + description: 'size of the DNS query ', + name: 'coredns.query.size', + type: 'integer', + format: 'bytes', + }, + 'coredns.query.class': { + category: 'coredns', + description: 'DNS query class ', + name: 'coredns.query.class', + type: 'keyword', + }, + 'coredns.query.name': { + category: 'coredns', + description: 'DNS query name ', + name: 'coredns.query.name', + type: 'keyword', + }, + 'coredns.query.type': { + category: 'coredns', + description: 'DNS query type ', + name: 'coredns.query.type', + type: 'keyword', + }, + 'coredns.response.code': { + category: 'coredns', + description: 'DNS response code ', + name: 'coredns.response.code', + type: 'keyword', + }, + 'coredns.response.flags': { + category: 'coredns', + description: 'DNS response flags ', + name: 'coredns.response.flags', + type: 'keyword', + }, + 'coredns.response.size': { + category: 'coredns', + description: 'size of the DNS response ', + name: 'coredns.response.size', + type: 'integer', + format: 'bytes', + }, + 'coredns.dnssec_ok': { + category: 'coredns', + description: 'dnssec flag ', + name: 'coredns.dnssec_ok', + type: 'boolean', + }, + 'crowdstrike.metadata.eventType': { + category: 'crowdstrike', + description: + 'DetectionSummaryEvent, FirewallMatchEvent, IncidentSummaryEvent, RemoteResponseSessionStartEvent, RemoteResponseSessionEndEvent, AuthActivityAuditEvent, or UserActivityAuditEvent ', + name: 'crowdstrike.metadata.eventType', + type: 'keyword', + }, + 'crowdstrike.metadata.eventCreationTime': { + category: 'crowdstrike', + description: 'The time this event occurred on the endpoint in UTC UNIX_MS format. ', + name: 'crowdstrike.metadata.eventCreationTime', + type: 'date', + }, + 'crowdstrike.metadata.offset': { + category: 'crowdstrike', + description: + 'Offset number that tracks the location of the event in stream. This is used to identify unique detection events. ', + name: 'crowdstrike.metadata.offset', + type: 'integer', + }, + 'crowdstrike.metadata.customerIDString': { + category: 'crowdstrike', + description: 'Customer identifier ', + name: 'crowdstrike.metadata.customerIDString', + type: 'keyword', + }, + 'crowdstrike.metadata.version': { + category: 'crowdstrike', + description: 'Schema version ', + name: 'crowdstrike.metadata.version', + type: 'keyword', + }, + 'crowdstrike.event.ProcessStartTime': { + category: 'crowdstrike', + description: 'The process start time in UTC UNIX_MS format. ', + name: 'crowdstrike.event.ProcessStartTime', + type: 'date', + }, + 'crowdstrike.event.ProcessEndTime': { + category: 'crowdstrike', + description: 'The process termination time in UTC UNIX_MS format. ', + name: 'crowdstrike.event.ProcessEndTime', + type: 'date', + }, + 'crowdstrike.event.ProcessId': { + category: 'crowdstrike', + description: 'Process ID related to the detection. ', + name: 'crowdstrike.event.ProcessId', + type: 'integer', + }, + 'crowdstrike.event.ParentProcessId': { + category: 'crowdstrike', + description: 'Parent process ID related to the detection. ', + name: 'crowdstrike.event.ParentProcessId', + type: 'integer', + }, + 'crowdstrike.event.ComputerName': { + category: 'crowdstrike', + description: 'Name of the computer where the detection occurred. ', + name: 'crowdstrike.event.ComputerName', + type: 'keyword', + }, + 'crowdstrike.event.UserName': { + category: 'crowdstrike', + description: 'User name associated with the detection. ', + name: 'crowdstrike.event.UserName', + type: 'keyword', + }, + 'crowdstrike.event.DetectName': { + category: 'crowdstrike', + description: 'Name of the detection. ', + name: 'crowdstrike.event.DetectName', + type: 'keyword', + }, + 'crowdstrike.event.DetectDescription': { + category: 'crowdstrike', + description: 'Description of the detection. ', + name: 'crowdstrike.event.DetectDescription', + type: 'keyword', + }, + 'crowdstrike.event.Severity': { + category: 'crowdstrike', + description: 'Severity score of the detection. ', + name: 'crowdstrike.event.Severity', + type: 'integer', + }, + 'crowdstrike.event.SeverityName': { + category: 'crowdstrike', + description: 'Severity score text. ', + name: 'crowdstrike.event.SeverityName', + type: 'keyword', + }, + 'crowdstrike.event.FileName': { + category: 'crowdstrike', + description: 'File name of the associated process for the detection. ', + name: 'crowdstrike.event.FileName', + type: 'keyword', + }, + 'crowdstrike.event.FilePath': { + category: 'crowdstrike', + description: 'Path of the executable associated with the detection. ', + name: 'crowdstrike.event.FilePath', + type: 'keyword', + }, + 'crowdstrike.event.CommandLine': { + category: 'crowdstrike', + description: 'Executable path with command line arguments. ', + name: 'crowdstrike.event.CommandLine', + type: 'keyword', + }, + 'crowdstrike.event.SHA1String': { + category: 'crowdstrike', + description: 'SHA1 sum of the executable associated with the detection. ', + name: 'crowdstrike.event.SHA1String', + type: 'keyword', + }, + 'crowdstrike.event.SHA256String': { + category: 'crowdstrike', + description: 'SHA256 sum of the executable associated with the detection. ', + name: 'crowdstrike.event.SHA256String', + type: 'keyword', + }, + 'crowdstrike.event.MD5String': { + category: 'crowdstrike', + description: 'MD5 sum of the executable associated with the detection. ', + name: 'crowdstrike.event.MD5String', + type: 'keyword', + }, + 'crowdstrike.event.MachineDomain': { + category: 'crowdstrike', + description: 'Domain for the machine associated with the detection. ', + name: 'crowdstrike.event.MachineDomain', + type: 'keyword', + }, + 'crowdstrike.event.FalconHostLink': { + category: 'crowdstrike', + description: 'URL to view the detection in Falcon. ', + name: 'crowdstrike.event.FalconHostLink', + type: 'keyword', + }, + 'crowdstrike.event.SensorId': { + category: 'crowdstrike', + description: 'Unique ID associated with the Falcon sensor. ', + name: 'crowdstrike.event.SensorId', + type: 'keyword', + }, + 'crowdstrike.event.DetectId': { + category: 'crowdstrike', + description: 'Unique ID associated with the detection. ', + name: 'crowdstrike.event.DetectId', + type: 'keyword', + }, + 'crowdstrike.event.LocalIP': { + category: 'crowdstrike', + description: 'IP address of the host associated with the detection. ', + name: 'crowdstrike.event.LocalIP', + type: 'keyword', + }, + 'crowdstrike.event.MACAddress': { + category: 'crowdstrike', + description: 'MAC address of the host associated with the detection. ', + name: 'crowdstrike.event.MACAddress', + type: 'keyword', + }, + 'crowdstrike.event.Tactic': { + category: 'crowdstrike', + description: 'MITRE tactic category of the detection. ', + name: 'crowdstrike.event.Tactic', + type: 'keyword', + }, + 'crowdstrike.event.Technique': { + category: 'crowdstrike', + description: 'MITRE technique category of the detection. ', + name: 'crowdstrike.event.Technique', + type: 'keyword', + }, + 'crowdstrike.event.Objective': { + category: 'crowdstrike', + description: 'Method of detection. ', + name: 'crowdstrike.event.Objective', + type: 'keyword', + }, + 'crowdstrike.event.PatternDispositionDescription': { + category: 'crowdstrike', + description: 'Action taken by Falcon. ', + name: 'crowdstrike.event.PatternDispositionDescription', + type: 'keyword', + }, + 'crowdstrike.event.PatternDispositionValue': { + category: 'crowdstrike', + description: 'Unique ID associated with action taken. ', + name: 'crowdstrike.event.PatternDispositionValue', + type: 'integer', + }, + 'crowdstrike.event.PatternDispositionFlags': { + category: 'crowdstrike', + description: 'Flags indicating actions taken. ', + name: 'crowdstrike.event.PatternDispositionFlags', + type: 'object', + }, + 'crowdstrike.event.State': { + category: 'crowdstrike', + description: 'Whether the incident summary is open and ongoing or closed. ', + name: 'crowdstrike.event.State', + type: 'keyword', + }, + 'crowdstrike.event.IncidentStartTime': { + category: 'crowdstrike', + description: 'Start time for the incident in UTC UNIX format. ', + name: 'crowdstrike.event.IncidentStartTime', + type: 'date', + }, + 'crowdstrike.event.IncidentEndTime': { + category: 'crowdstrike', + description: 'End time for the incident in UTC UNIX format. ', + name: 'crowdstrike.event.IncidentEndTime', + type: 'date', + }, + 'crowdstrike.event.FineScore': { + category: 'crowdstrike', + description: 'Score for incident. ', + name: 'crowdstrike.event.FineScore', + type: 'float', + }, + 'crowdstrike.event.UserId': { + category: 'crowdstrike', + description: 'Email address or user ID associated with the event. ', + name: 'crowdstrike.event.UserId', + type: 'keyword', + }, + 'crowdstrike.event.UserIp': { + category: 'crowdstrike', + description: 'IP address associated with the user. ', + name: 'crowdstrike.event.UserIp', + type: 'keyword', + }, + 'crowdstrike.event.OperationName': { + category: 'crowdstrike', + description: 'Event subtype. ', + name: 'crowdstrike.event.OperationName', + type: 'keyword', + }, + 'crowdstrike.event.ServiceName': { + category: 'crowdstrike', + description: 'Service associated with this event. ', + name: 'crowdstrike.event.ServiceName', + type: 'keyword', + }, + 'crowdstrike.event.Success': { + category: 'crowdstrike', + description: 'Indicator of whether or not this event was successful. ', + name: 'crowdstrike.event.Success', + type: 'boolean', + }, + 'crowdstrike.event.UTCTimestamp': { + category: 'crowdstrike', + description: 'Timestamp associated with this event in UTC UNIX format. ', + name: 'crowdstrike.event.UTCTimestamp', + type: 'date', + }, + 'crowdstrike.event.AuditKeyValues': { + category: 'crowdstrike', + description: 'Fields that were changed in this event. ', + name: 'crowdstrike.event.AuditKeyValues', + type: 'nested', + }, + 'crowdstrike.event.ExecutablesWritten': { + category: 'crowdstrike', + description: 'Detected executables written to disk by a process. ', + name: 'crowdstrike.event.ExecutablesWritten', + type: 'nested', + }, + 'crowdstrike.event.SessionId': { + category: 'crowdstrike', + description: 'Session ID of the remote response session. ', + name: 'crowdstrike.event.SessionId', + type: 'keyword', + }, + 'crowdstrike.event.HostnameField': { + category: 'crowdstrike', + description: 'Host name of the machine for the remote session. ', + name: 'crowdstrike.event.HostnameField', + type: 'keyword', + }, + 'crowdstrike.event.StartTimestamp': { + category: 'crowdstrike', + description: 'Start time for the remote session in UTC UNIX format. ', + name: 'crowdstrike.event.StartTimestamp', + type: 'date', + }, + 'crowdstrike.event.EndTimestamp': { + category: 'crowdstrike', + description: 'End time for the remote session in UTC UNIX format. ', + name: 'crowdstrike.event.EndTimestamp', + type: 'date', + }, + 'crowdstrike.event.LateralMovement': { + category: 'crowdstrike', + description: 'Lateral movement field for incident. ', + name: 'crowdstrike.event.LateralMovement', + type: 'long', + }, + 'crowdstrike.event.ParentImageFileName': { + category: 'crowdstrike', + description: 'Path to the parent process. ', + name: 'crowdstrike.event.ParentImageFileName', + type: 'keyword', + }, + 'crowdstrike.event.ParentCommandLine': { + category: 'crowdstrike', + description: 'Parent process command line arguments. ', + name: 'crowdstrike.event.ParentCommandLine', + type: 'keyword', + }, + 'crowdstrike.event.GrandparentImageFileName': { + category: 'crowdstrike', + description: 'Path to the grandparent process. ', + name: 'crowdstrike.event.GrandparentImageFileName', + type: 'keyword', + }, + 'crowdstrike.event.GrandparentCommandLine': { + category: 'crowdstrike', + description: 'Grandparent process command line arguments. ', + name: 'crowdstrike.event.GrandparentCommandLine', + type: 'keyword', + }, + 'crowdstrike.event.IOCType': { + category: 'crowdstrike', + description: 'CrowdStrike type for indicator of compromise. ', + name: 'crowdstrike.event.IOCType', + type: 'keyword', + }, + 'crowdstrike.event.IOCValue': { + category: 'crowdstrike', + description: 'CrowdStrike value for indicator of compromise. ', + name: 'crowdstrike.event.IOCValue', + type: 'keyword', + }, + 'crowdstrike.event.CustomerId': { + category: 'crowdstrike', + description: 'Customer identifier. ', + name: 'crowdstrike.event.CustomerId', + type: 'keyword', + }, + 'crowdstrike.event.DeviceId': { + category: 'crowdstrike', + description: 'Device on which the event occurred. ', + name: 'crowdstrike.event.DeviceId', + type: 'keyword', + }, + 'crowdstrike.event.Ipv': { + category: 'crowdstrike', + description: 'Protocol for network request. ', + name: 'crowdstrike.event.Ipv', + type: 'keyword', + }, + 'crowdstrike.event.ConnectionDirection': { + category: 'crowdstrike', + description: 'Direction for network connection. ', + name: 'crowdstrike.event.ConnectionDirection', + type: 'keyword', + }, + 'crowdstrike.event.EventType': { + category: 'crowdstrike', + description: 'CrowdStrike provided event type. ', + name: 'crowdstrike.event.EventType', + type: 'keyword', + }, + 'crowdstrike.event.HostName': { + category: 'crowdstrike', + description: 'Host name of the local machine. ', + name: 'crowdstrike.event.HostName', + type: 'keyword', + }, + 'crowdstrike.event.ICMPCode': { + category: 'crowdstrike', + description: 'RFC2780 ICMP Code field. ', + name: 'crowdstrike.event.ICMPCode', + type: 'keyword', + }, + 'crowdstrike.event.ICMPType': { + category: 'crowdstrike', + description: 'RFC2780 ICMP Type field. ', + name: 'crowdstrike.event.ICMPType', + type: 'keyword', + }, + 'crowdstrike.event.ImageFileName': { + category: 'crowdstrike', + description: 'File name of the associated process for the detection. ', + name: 'crowdstrike.event.ImageFileName', + type: 'keyword', + }, + 'crowdstrike.event.PID': { + category: 'crowdstrike', + description: 'Associated process id for the detection. ', + name: 'crowdstrike.event.PID', + type: 'long', + }, + 'crowdstrike.event.LocalAddress': { + category: 'crowdstrike', + description: 'IP address of local machine. ', + name: 'crowdstrike.event.LocalAddress', + type: 'ip', + }, + 'crowdstrike.event.LocalPort': { + category: 'crowdstrike', + description: 'Port of local machine. ', + name: 'crowdstrike.event.LocalPort', + type: 'long', + }, + 'crowdstrike.event.RemoteAddress': { + category: 'crowdstrike', + description: 'IP address of remote machine. ', + name: 'crowdstrike.event.RemoteAddress', + type: 'ip', + }, + 'crowdstrike.event.RemotePort': { + category: 'crowdstrike', + description: 'Port of remote machine. ', + name: 'crowdstrike.event.RemotePort', + type: 'long', + }, + 'crowdstrike.event.RuleAction': { + category: 'crowdstrike', + description: 'Firewall rule action. ', + name: 'crowdstrike.event.RuleAction', + type: 'keyword', + }, + 'crowdstrike.event.RuleDescription': { + category: 'crowdstrike', + description: 'Firewall rule description. ', + name: 'crowdstrike.event.RuleDescription', + type: 'keyword', + }, + 'crowdstrike.event.RuleFamilyID': { + category: 'crowdstrike', + description: 'Firewall rule family id. ', + name: 'crowdstrike.event.RuleFamilyID', + type: 'keyword', + }, + 'crowdstrike.event.RuleGroupName': { + category: 'crowdstrike', + description: 'Firewall rule group name. ', + name: 'crowdstrike.event.RuleGroupName', + type: 'keyword', + }, + 'crowdstrike.event.RuleName': { + category: 'crowdstrike', + description: 'Firewall rule name. ', + name: 'crowdstrike.event.RuleName', + type: 'keyword', + }, + 'crowdstrike.event.RuleId': { + category: 'crowdstrike', + description: 'Firewall rule id. ', + name: 'crowdstrike.event.RuleId', + type: 'keyword', + }, + 'crowdstrike.event.MatchCount': { + category: 'crowdstrike', + description: 'Number of firewall rule matches. ', + name: 'crowdstrike.event.MatchCount', + type: 'long', + }, + 'crowdstrike.event.MatchCountSinceLastReport': { + category: 'crowdstrike', + description: 'Number of firewall rule matches since the last report. ', + name: 'crowdstrike.event.MatchCountSinceLastReport', + type: 'long', + }, + 'crowdstrike.event.Timestamp': { + category: 'crowdstrike', + description: 'Firewall rule triggered timestamp. ', + name: 'crowdstrike.event.Timestamp', + type: 'date', + }, + 'crowdstrike.event.Flags.Audit': { + category: 'crowdstrike', + description: 'CrowdStrike audit flag. ', + name: 'crowdstrike.event.Flags.Audit', + type: 'boolean', + }, + 'crowdstrike.event.Flags.Log': { + category: 'crowdstrike', + description: 'CrowdStrike log flag. ', + name: 'crowdstrike.event.Flags.Log', + type: 'boolean', + }, + 'crowdstrike.event.Flags.Monitor': { + category: 'crowdstrike', + description: 'CrowdStrike monitor flag. ', + name: 'crowdstrike.event.Flags.Monitor', + type: 'boolean', + }, + 'crowdstrike.event.Protocol': { + category: 'crowdstrike', + description: 'CrowdStrike provided protocol. ', + name: 'crowdstrike.event.Protocol', + type: 'keyword', + }, + 'crowdstrike.event.NetworkProfile': { + category: 'crowdstrike', + description: 'CrowdStrike network profile. ', + name: 'crowdstrike.event.NetworkProfile', + type: 'keyword', + }, + 'crowdstrike.event.PolicyName': { + category: 'crowdstrike', + description: 'CrowdStrike policy name. ', + name: 'crowdstrike.event.PolicyName', + type: 'keyword', + }, + 'crowdstrike.event.PolicyID': { + category: 'crowdstrike', + description: 'CrowdStrike policy id. ', + name: 'crowdstrike.event.PolicyID', + type: 'keyword', + }, + 'crowdstrike.event.Status': { + category: 'crowdstrike', + description: 'CrowdStrike status. ', + name: 'crowdstrike.event.Status', + type: 'keyword', + }, + 'crowdstrike.event.TreeID': { + category: 'crowdstrike', + description: 'CrowdStrike tree id. ', + name: 'crowdstrike.event.TreeID', + type: 'keyword', + }, + 'crowdstrike.event.Commands': { + category: 'crowdstrike', + description: 'Commands run in a remote session. ', + name: 'crowdstrike.event.Commands', + type: 'keyword', + }, + 'envoyproxy.log_type': { + category: 'envoyproxy', + description: 'Envoy log type, normally ACCESS ', + name: 'envoyproxy.log_type', + type: 'keyword', + }, + 'envoyproxy.response_flags': { + category: 'envoyproxy', + description: 'Response flags ', + name: 'envoyproxy.response_flags', + type: 'keyword', + }, + 'envoyproxy.upstream_service_time': { + category: 'envoyproxy', + description: 'Upstream service time in nanoseconds ', + name: 'envoyproxy.upstream_service_time', + type: 'long', + format: 'duration', + }, + 'envoyproxy.request_id': { + category: 'envoyproxy', + description: 'ID of the request ', + name: 'envoyproxy.request_id', + type: 'keyword', + }, + 'envoyproxy.authority': { + category: 'envoyproxy', + description: 'Envoy proxy authority field ', + name: 'envoyproxy.authority', + type: 'keyword', + }, + 'envoyproxy.proxy_type': { + category: 'envoyproxy', + description: 'Envoy proxy type, tcp or http ', + name: 'envoyproxy.proxy_type', + type: 'keyword', + }, + 'fortinet.file.hash.crc32': { + category: 'fortinet', + description: 'CRC32 Hash of file ', + name: 'fortinet.file.hash.crc32', + type: 'keyword', + }, + 'fortinet.firewall.acct_stat': { + category: 'fortinet', + description: 'Accounting state (RADIUS) ', + name: 'fortinet.firewall.acct_stat', + type: 'keyword', + }, + 'fortinet.firewall.acktime': { + category: 'fortinet', + description: 'Alarm Acknowledge Time ', + name: 'fortinet.firewall.acktime', + type: 'keyword', + }, + 'fortinet.firewall.act': { + category: 'fortinet', + description: 'Action ', + name: 'fortinet.firewall.act', + type: 'keyword', + }, + 'fortinet.firewall.action': { + category: 'fortinet', + description: 'Status of the session ', + name: 'fortinet.firewall.action', + type: 'keyword', + }, + 'fortinet.firewall.activity': { + category: 'fortinet', + description: 'HA activity message ', + name: 'fortinet.firewall.activity', + type: 'keyword', + }, + 'fortinet.firewall.addr': { + category: 'fortinet', + description: 'IP Address ', + name: 'fortinet.firewall.addr', + type: 'ip', + }, + 'fortinet.firewall.addr_type': { + category: 'fortinet', + description: 'Address Type ', + name: 'fortinet.firewall.addr_type', + type: 'keyword', + }, + 'fortinet.firewall.addrgrp': { + category: 'fortinet', + description: 'Address Group ', + name: 'fortinet.firewall.addrgrp', + type: 'keyword', + }, + 'fortinet.firewall.adgroup': { + category: 'fortinet', + description: 'AD Group Name ', + name: 'fortinet.firewall.adgroup', + type: 'keyword', + }, + 'fortinet.firewall.admin': { + category: 'fortinet', + description: 'Admin User ', + name: 'fortinet.firewall.admin', + type: 'keyword', + }, + 'fortinet.firewall.age': { + category: 'fortinet', + description: 'Time in seconds - time passed since last seen ', + name: 'fortinet.firewall.age', + type: 'integer', + }, + 'fortinet.firewall.agent': { + category: 'fortinet', + description: 'User agent - eg. agent="Mozilla/5.0" ', + name: 'fortinet.firewall.agent', + type: 'keyword', + }, + 'fortinet.firewall.alarmid': { + category: 'fortinet', + description: 'Alarm ID ', + name: 'fortinet.firewall.alarmid', + type: 'integer', + }, + 'fortinet.firewall.alert': { + category: 'fortinet', + description: 'Alert ', + name: 'fortinet.firewall.alert', + type: 'keyword', + }, + 'fortinet.firewall.analyticscksum': { + category: 'fortinet', + description: 'The checksum of the file submitted for analytics ', + name: 'fortinet.firewall.analyticscksum', + type: 'keyword', + }, + 'fortinet.firewall.analyticssubmit': { + category: 'fortinet', + description: 'The flag for analytics submission ', + name: 'fortinet.firewall.analyticssubmit', + type: 'keyword', + }, + 'fortinet.firewall.ap': { + category: 'fortinet', + description: 'Access Point ', + name: 'fortinet.firewall.ap', + type: 'keyword', + }, + 'fortinet.firewall.app-type': { + category: 'fortinet', + description: 'Address Type ', + name: 'fortinet.firewall.app-type', + type: 'keyword', + }, + 'fortinet.firewall.appact': { + category: 'fortinet', + description: 'The security action from app control ', + name: 'fortinet.firewall.appact', + type: 'keyword', + }, + 'fortinet.firewall.appid': { + category: 'fortinet', + description: 'Application ID ', + name: 'fortinet.firewall.appid', + type: 'integer', + }, + 'fortinet.firewall.applist': { + category: 'fortinet', + description: 'Application Control profile ', + name: 'fortinet.firewall.applist', + type: 'keyword', + }, + 'fortinet.firewall.apprisk': { + category: 'fortinet', + description: 'Application Risk Level ', + name: 'fortinet.firewall.apprisk', + type: 'keyword', + }, + 'fortinet.firewall.apscan': { + category: 'fortinet', + description: 'The name of the AP, which scanned and detected the rogue AP ', + name: 'fortinet.firewall.apscan', + type: 'keyword', + }, + 'fortinet.firewall.apsn': { + category: 'fortinet', + description: 'Access Point ', + name: 'fortinet.firewall.apsn', + type: 'keyword', + }, + 'fortinet.firewall.apstatus': { + category: 'fortinet', + description: 'Access Point status ', + name: 'fortinet.firewall.apstatus', + type: 'keyword', + }, + 'fortinet.firewall.aptype': { + category: 'fortinet', + description: 'Access Point type ', + name: 'fortinet.firewall.aptype', + type: 'keyword', + }, + 'fortinet.firewall.assigned': { + category: 'fortinet', + description: 'Assigned IP Address ', + name: 'fortinet.firewall.assigned', + type: 'ip', + }, + 'fortinet.firewall.assignip': { + category: 'fortinet', + description: 'Assigned IP Address ', + name: 'fortinet.firewall.assignip', + type: 'ip', + }, + 'fortinet.firewall.attachment': { + category: 'fortinet', + description: 'The flag for email attachement ', + name: 'fortinet.firewall.attachment', + type: 'keyword', + }, + 'fortinet.firewall.attack': { + category: 'fortinet', + description: 'Attack Name ', + name: 'fortinet.firewall.attack', + type: 'keyword', + }, + 'fortinet.firewall.attackcontext': { + category: 'fortinet', + description: 'The trigger patterns and the packetdata with base64 encoding ', + name: 'fortinet.firewall.attackcontext', + type: 'keyword', + }, + 'fortinet.firewall.attackcontextid': { + category: 'fortinet', + description: 'Attack context id / total ', + name: 'fortinet.firewall.attackcontextid', + type: 'keyword', + }, + 'fortinet.firewall.attackid': { + category: 'fortinet', + description: 'Attack ID ', + name: 'fortinet.firewall.attackid', + type: 'integer', + }, + 'fortinet.firewall.auditid': { + category: 'fortinet', + description: 'Audit ID ', + name: 'fortinet.firewall.auditid', + type: 'long', + }, + 'fortinet.firewall.auditscore': { + category: 'fortinet', + description: 'The Audit Score ', + name: 'fortinet.firewall.auditscore', + type: 'keyword', + }, + 'fortinet.firewall.audittime': { + category: 'fortinet', + description: 'The time of the audit ', + name: 'fortinet.firewall.audittime', + type: 'long', + }, + 'fortinet.firewall.authgrp': { + category: 'fortinet', + description: 'Authorization Group ', + name: 'fortinet.firewall.authgrp', + type: 'keyword', + }, + 'fortinet.firewall.authid': { + category: 'fortinet', + description: 'Authentication ID ', + name: 'fortinet.firewall.authid', + type: 'keyword', + }, + 'fortinet.firewall.authproto': { + category: 'fortinet', + description: 'The protocol that initiated the authentication ', + name: 'fortinet.firewall.authproto', + type: 'keyword', + }, + 'fortinet.firewall.authserver': { + category: 'fortinet', + description: 'Authentication server ', + name: 'fortinet.firewall.authserver', + type: 'keyword', + }, + 'fortinet.firewall.bandwidth': { + category: 'fortinet', + description: 'Bandwidth ', + name: 'fortinet.firewall.bandwidth', + type: 'keyword', + }, + 'fortinet.firewall.banned_rule': { + category: 'fortinet', + description: 'NAC quarantine Banned Rule Name ', + name: 'fortinet.firewall.banned_rule', + type: 'keyword', + }, + 'fortinet.firewall.banned_src': { + category: 'fortinet', + description: 'NAC quarantine Banned Source IP ', + name: 'fortinet.firewall.banned_src', + type: 'keyword', + }, + 'fortinet.firewall.banword': { + category: 'fortinet', + description: 'Banned word ', + name: 'fortinet.firewall.banword', + type: 'keyword', + }, + 'fortinet.firewall.botnetdomain': { + category: 'fortinet', + description: 'Botnet Domain Name ', + name: 'fortinet.firewall.botnetdomain', + type: 'keyword', + }, + 'fortinet.firewall.botnetip': { + category: 'fortinet', + description: 'Botnet IP Address ', + name: 'fortinet.firewall.botnetip', + type: 'ip', + }, + 'fortinet.firewall.bssid': { + category: 'fortinet', + description: 'Service Set ID ', + name: 'fortinet.firewall.bssid', + type: 'keyword', + }, + 'fortinet.firewall.call_id': { + category: 'fortinet', + description: 'Caller ID ', + name: 'fortinet.firewall.call_id', + type: 'keyword', + }, + 'fortinet.firewall.carrier_ep': { + category: 'fortinet', + description: 'The FortiOS Carrier end-point identification ', + name: 'fortinet.firewall.carrier_ep', + type: 'keyword', + }, + 'fortinet.firewall.cat': { + category: 'fortinet', + description: 'DNS category ID ', + name: 'fortinet.firewall.cat', + type: 'integer', + }, + 'fortinet.firewall.category': { + category: 'fortinet', + description: 'Authentication category ', + name: 'fortinet.firewall.category', + type: 'keyword', + }, + 'fortinet.firewall.cc': { + category: 'fortinet', + description: 'CC Email Address ', + name: 'fortinet.firewall.cc', + type: 'keyword', + }, + 'fortinet.firewall.cdrcontent': { + category: 'fortinet', + description: 'Cdrcontent ', + name: 'fortinet.firewall.cdrcontent', + type: 'keyword', + }, + 'fortinet.firewall.centralnatid': { + category: 'fortinet', + description: 'Central NAT ID ', + name: 'fortinet.firewall.centralnatid', + type: 'integer', + }, + 'fortinet.firewall.cert': { + category: 'fortinet', + description: 'Certificate ', + name: 'fortinet.firewall.cert', + type: 'keyword', + }, + 'fortinet.firewall.cert-type': { + category: 'fortinet', + description: 'Certificate type ', + name: 'fortinet.firewall.cert-type', + type: 'keyword', + }, + 'fortinet.firewall.certhash': { + category: 'fortinet', + description: 'Certificate hash ', + name: 'fortinet.firewall.certhash', + type: 'keyword', + }, + 'fortinet.firewall.cfgattr': { + category: 'fortinet', + description: 'Configuration attribute ', + name: 'fortinet.firewall.cfgattr', + type: 'keyword', + }, + 'fortinet.firewall.cfgobj': { + category: 'fortinet', + description: 'Configuration object ', + name: 'fortinet.firewall.cfgobj', + type: 'keyword', + }, + 'fortinet.firewall.cfgpath': { + category: 'fortinet', + description: 'Configuration path ', + name: 'fortinet.firewall.cfgpath', + type: 'keyword', + }, + 'fortinet.firewall.cfgtid': { + category: 'fortinet', + description: 'Configuration transaction ID ', + name: 'fortinet.firewall.cfgtid', + type: 'keyword', + }, + 'fortinet.firewall.cfgtxpower': { + category: 'fortinet', + description: 'Configuration TX power ', + name: 'fortinet.firewall.cfgtxpower', + type: 'integer', + }, + 'fortinet.firewall.channel': { + category: 'fortinet', + description: 'Wireless Channel ', + name: 'fortinet.firewall.channel', + type: 'integer', + }, + 'fortinet.firewall.channeltype': { + category: 'fortinet', + description: 'SSH channel type ', + name: 'fortinet.firewall.channeltype', + type: 'keyword', + }, + 'fortinet.firewall.chassisid': { + category: 'fortinet', + description: 'Chassis ID ', + name: 'fortinet.firewall.chassisid', + type: 'integer', + }, + 'fortinet.firewall.checksum': { + category: 'fortinet', + description: 'The checksum of the scanned file ', + name: 'fortinet.firewall.checksum', + type: 'keyword', + }, + 'fortinet.firewall.chgheaders': { + category: 'fortinet', + description: 'HTTP Headers ', + name: 'fortinet.firewall.chgheaders', + type: 'keyword', + }, + 'fortinet.firewall.cldobjid': { + category: 'fortinet', + description: 'Connector object ID ', + name: 'fortinet.firewall.cldobjid', + type: 'keyword', + }, + 'fortinet.firewall.client_addr': { + category: 'fortinet', + description: 'Wifi client address ', + name: 'fortinet.firewall.client_addr', + type: 'keyword', + }, + 'fortinet.firewall.cloudaction': { + category: 'fortinet', + description: 'Cloud Action ', + name: 'fortinet.firewall.cloudaction', + type: 'keyword', + }, + 'fortinet.firewall.clouduser': { + category: 'fortinet', + description: 'Cloud User ', + name: 'fortinet.firewall.clouduser', + type: 'keyword', + }, + 'fortinet.firewall.column': { + category: 'fortinet', + description: 'VOIP Column ', + name: 'fortinet.firewall.column', + type: 'integer', + }, + 'fortinet.firewall.command': { + category: 'fortinet', + description: 'CLI Command ', + name: 'fortinet.firewall.command', + type: 'keyword', + }, + 'fortinet.firewall.community': { + category: 'fortinet', + description: 'SNMP Community ', + name: 'fortinet.firewall.community', + type: 'keyword', + }, + 'fortinet.firewall.configcountry': { + category: 'fortinet', + description: 'Configuration country ', + name: 'fortinet.firewall.configcountry', + type: 'keyword', + }, + 'fortinet.firewall.connection_type': { + category: 'fortinet', + description: 'FortiClient Connection Type ', + name: 'fortinet.firewall.connection_type', + type: 'keyword', + }, + 'fortinet.firewall.conserve': { + category: 'fortinet', + description: 'Flag for conserve mode ', + name: 'fortinet.firewall.conserve', + type: 'keyword', + }, + 'fortinet.firewall.constraint': { + category: 'fortinet', + description: 'WAF http protocol restrictions ', + name: 'fortinet.firewall.constraint', + type: 'keyword', + }, + 'fortinet.firewall.contentdisarmed': { + category: 'fortinet', + description: 'Email scanned content ', + name: 'fortinet.firewall.contentdisarmed', + type: 'keyword', + }, + 'fortinet.firewall.contenttype': { + category: 'fortinet', + description: 'Content Type from HTTP header ', + name: 'fortinet.firewall.contenttype', + type: 'keyword', + }, + 'fortinet.firewall.cookies': { + category: 'fortinet', + description: 'VPN Cookie ', + name: 'fortinet.firewall.cookies', + type: 'keyword', + }, + 'fortinet.firewall.count': { + category: 'fortinet', + description: 'Counts of action type ', + name: 'fortinet.firewall.count', + type: 'integer', + }, + 'fortinet.firewall.countapp': { + category: 'fortinet', + description: 'Number of App Ctrl logs associated with the session ', + name: 'fortinet.firewall.countapp', + type: 'integer', + }, + 'fortinet.firewall.countav': { + category: 'fortinet', + description: 'Number of AV logs associated with the session ', + name: 'fortinet.firewall.countav', + type: 'integer', + }, + 'fortinet.firewall.countcifs': { + category: 'fortinet', + description: 'Number of CIFS logs associated with the session ', + name: 'fortinet.firewall.countcifs', + type: 'integer', + }, + 'fortinet.firewall.countdlp': { + category: 'fortinet', + description: 'Number of DLP logs associated with the session ', + name: 'fortinet.firewall.countdlp', + type: 'integer', + }, + 'fortinet.firewall.countdns': { + category: 'fortinet', + description: 'Number of DNS logs associated with the session ', + name: 'fortinet.firewall.countdns', + type: 'integer', + }, + 'fortinet.firewall.countemail': { + category: 'fortinet', + description: 'Number of email logs associated with the session ', + name: 'fortinet.firewall.countemail', + type: 'integer', + }, + 'fortinet.firewall.countff': { + category: 'fortinet', + description: 'Number of ff logs associated with the session ', + name: 'fortinet.firewall.countff', + type: 'integer', + }, + 'fortinet.firewall.countips': { + category: 'fortinet', + description: 'Number of IPS logs associated with the session ', + name: 'fortinet.firewall.countips', + type: 'integer', + }, + 'fortinet.firewall.countssh': { + category: 'fortinet', + description: 'Number of SSH logs associated with the session ', + name: 'fortinet.firewall.countssh', + type: 'integer', + }, + 'fortinet.firewall.countssl': { + category: 'fortinet', + description: 'Number of SSL logs associated with the session ', + name: 'fortinet.firewall.countssl', + type: 'integer', + }, + 'fortinet.firewall.countwaf': { + category: 'fortinet', + description: 'Number of WAF logs associated with the session ', + name: 'fortinet.firewall.countwaf', + type: 'integer', + }, + 'fortinet.firewall.countweb': { + category: 'fortinet', + description: 'Number of Web filter logs associated with the session ', + name: 'fortinet.firewall.countweb', + type: 'integer', + }, + 'fortinet.firewall.cpu': { + category: 'fortinet', + description: 'CPU Usage ', + name: 'fortinet.firewall.cpu', + type: 'integer', + }, + 'fortinet.firewall.craction': { + category: 'fortinet', + description: 'Client Reputation Action ', + name: 'fortinet.firewall.craction', + type: 'integer', + }, + 'fortinet.firewall.criticalcount': { + category: 'fortinet', + description: 'Number of critical ratings ', + name: 'fortinet.firewall.criticalcount', + type: 'integer', + }, + 'fortinet.firewall.crl': { + category: 'fortinet', + description: 'Client Reputation Level ', + name: 'fortinet.firewall.crl', + type: 'keyword', + }, + 'fortinet.firewall.crlevel': { + category: 'fortinet', + description: 'Client Reputation Level ', + name: 'fortinet.firewall.crlevel', + type: 'keyword', + }, + 'fortinet.firewall.crscore': { + category: 'fortinet', + description: 'Some description ', + name: 'fortinet.firewall.crscore', + type: 'integer', + }, + 'fortinet.firewall.cveid': { + category: 'fortinet', + description: 'CVE ID ', + name: 'fortinet.firewall.cveid', + type: 'keyword', + }, + 'fortinet.firewall.daemon': { + category: 'fortinet', + description: 'Daemon name ', + name: 'fortinet.firewall.daemon', + type: 'keyword', + }, + 'fortinet.firewall.datarange': { + category: 'fortinet', + description: 'Data range for reports ', + name: 'fortinet.firewall.datarange', + type: 'keyword', + }, + 'fortinet.firewall.date': { + category: 'fortinet', + description: 'Date ', + name: 'fortinet.firewall.date', + type: 'keyword', + }, + 'fortinet.firewall.ddnsserver': { + category: 'fortinet', + description: 'DDNS server ', + name: 'fortinet.firewall.ddnsserver', + type: 'ip', + }, + 'fortinet.firewall.desc': { + category: 'fortinet', + description: 'Description ', + name: 'fortinet.firewall.desc', + type: 'keyword', + }, + 'fortinet.firewall.detectionmethod': { + category: 'fortinet', + description: 'Detection method ', + name: 'fortinet.firewall.detectionmethod', + type: 'keyword', + }, + 'fortinet.firewall.devcategory': { + category: 'fortinet', + description: 'Device category ', + name: 'fortinet.firewall.devcategory', + type: 'keyword', + }, + 'fortinet.firewall.devintfname': { + category: 'fortinet', + description: 'HA device Interface Name ', + name: 'fortinet.firewall.devintfname', + type: 'keyword', + }, + 'fortinet.firewall.devtype': { + category: 'fortinet', + description: 'Device type ', + name: 'fortinet.firewall.devtype', + type: 'keyword', + }, + 'fortinet.firewall.dhcp_msg': { + category: 'fortinet', + description: 'DHCP Message ', + name: 'fortinet.firewall.dhcp_msg', + type: 'keyword', + }, + 'fortinet.firewall.dintf': { + category: 'fortinet', + description: 'Destination interface ', + name: 'fortinet.firewall.dintf', + type: 'keyword', + }, + 'fortinet.firewall.disk': { + category: 'fortinet', + description: 'Assosciated disk ', + name: 'fortinet.firewall.disk', + type: 'keyword', + }, + 'fortinet.firewall.disklograte': { + category: 'fortinet', + description: 'Disk logging rate ', + name: 'fortinet.firewall.disklograte', + type: 'long', + }, + 'fortinet.firewall.dlpextra': { + category: 'fortinet', + description: 'DLP extra information ', + name: 'fortinet.firewall.dlpextra', + type: 'keyword', + }, + 'fortinet.firewall.docsource': { + category: 'fortinet', + description: 'DLP fingerprint document source ', + name: 'fortinet.firewall.docsource', + type: 'keyword', + }, + 'fortinet.firewall.domainctrlauthstate': { + category: 'fortinet', + description: 'CIFS domain auth state ', + name: 'fortinet.firewall.domainctrlauthstate', + type: 'integer', + }, + 'fortinet.firewall.domainctrlauthtype': { + category: 'fortinet', + description: 'CIFS domain auth type ', + name: 'fortinet.firewall.domainctrlauthtype', + type: 'integer', + }, + 'fortinet.firewall.domainctrldomain': { + category: 'fortinet', + description: 'CIFS domain auth domain ', + name: 'fortinet.firewall.domainctrldomain', + type: 'keyword', + }, + 'fortinet.firewall.domainctrlip': { + category: 'fortinet', + description: 'CIFS Domain IP ', + name: 'fortinet.firewall.domainctrlip', + type: 'ip', + }, + 'fortinet.firewall.domainctrlname': { + category: 'fortinet', + description: 'CIFS Domain name ', + name: 'fortinet.firewall.domainctrlname', + type: 'keyword', + }, + 'fortinet.firewall.domainctrlprotocoltype': { + category: 'fortinet', + description: 'CIFS Domain connection protocol ', + name: 'fortinet.firewall.domainctrlprotocoltype', + type: 'integer', + }, + 'fortinet.firewall.domainctrlusername': { + category: 'fortinet', + description: 'CIFS Domain username ', + name: 'fortinet.firewall.domainctrlusername', + type: 'keyword', + }, + 'fortinet.firewall.domainfilteridx': { + category: 'fortinet', + description: 'Domain filter ID ', + name: 'fortinet.firewall.domainfilteridx', + type: 'integer', + }, + 'fortinet.firewall.domainfilterlist': { + category: 'fortinet', + description: 'Domain filter name ', + name: 'fortinet.firewall.domainfilterlist', + type: 'keyword', + }, + 'fortinet.firewall.ds': { + category: 'fortinet', + description: 'Direction with distribution system ', + name: 'fortinet.firewall.ds', + type: 'keyword', + }, + 'fortinet.firewall.dst_int': { + category: 'fortinet', + description: 'Destination interface ', + name: 'fortinet.firewall.dst_int', + type: 'keyword', + }, + 'fortinet.firewall.dstintfrole': { + category: 'fortinet', + description: 'Destination interface role ', + name: 'fortinet.firewall.dstintfrole', + type: 'keyword', + }, + 'fortinet.firewall.dstcountry': { + category: 'fortinet', + description: 'Destination country ', + name: 'fortinet.firewall.dstcountry', + type: 'keyword', + }, + 'fortinet.firewall.dstdevcategory': { + category: 'fortinet', + description: 'Destination device category ', + name: 'fortinet.firewall.dstdevcategory', + type: 'keyword', + }, + 'fortinet.firewall.dstdevtype': { + category: 'fortinet', + description: 'Destination device type ', + name: 'fortinet.firewall.dstdevtype', + type: 'keyword', + }, + 'fortinet.firewall.dstfamily': { + category: 'fortinet', + description: 'Destination OS family ', + name: 'fortinet.firewall.dstfamily', + type: 'keyword', + }, + 'fortinet.firewall.dsthwvendor': { + category: 'fortinet', + description: 'Destination HW vendor ', + name: 'fortinet.firewall.dsthwvendor', + type: 'keyword', + }, + 'fortinet.firewall.dsthwversion': { + category: 'fortinet', + description: 'Destination HW version ', + name: 'fortinet.firewall.dsthwversion', + type: 'keyword', + }, + 'fortinet.firewall.dstinetsvc': { + category: 'fortinet', + description: 'Destination interface service ', + name: 'fortinet.firewall.dstinetsvc', + type: 'keyword', + }, + 'fortinet.firewall.dstosname': { + category: 'fortinet', + description: 'Destination OS name ', + name: 'fortinet.firewall.dstosname', + type: 'keyword', + }, + 'fortinet.firewall.dstosversion': { + category: 'fortinet', + description: 'Destination OS version ', + name: 'fortinet.firewall.dstosversion', + type: 'keyword', + }, + 'fortinet.firewall.dstserver': { + category: 'fortinet', + description: 'Destination server ', + name: 'fortinet.firewall.dstserver', + type: 'integer', + }, + 'fortinet.firewall.dstssid': { + category: 'fortinet', + description: 'Destination SSID ', + name: 'fortinet.firewall.dstssid', + type: 'keyword', + }, + 'fortinet.firewall.dstswversion': { + category: 'fortinet', + description: 'Destination software version ', + name: 'fortinet.firewall.dstswversion', + type: 'keyword', + }, + 'fortinet.firewall.dstunauthusersource': { + category: 'fortinet', + description: 'Destination unauthenticated source ', + name: 'fortinet.firewall.dstunauthusersource', + type: 'keyword', + }, + 'fortinet.firewall.dstuuid': { + category: 'fortinet', + description: 'UUID of the Destination IP address ', + name: 'fortinet.firewall.dstuuid', + type: 'keyword', + }, + 'fortinet.firewall.duid': { + category: 'fortinet', + description: 'DHCP UID ', + name: 'fortinet.firewall.duid', + type: 'keyword', + }, + 'fortinet.firewall.eapolcnt': { + category: 'fortinet', + description: 'EAPOL packet count ', + name: 'fortinet.firewall.eapolcnt', + type: 'integer', + }, + 'fortinet.firewall.eapoltype': { + category: 'fortinet', + description: 'EAPOL packet type ', + name: 'fortinet.firewall.eapoltype', + type: 'keyword', + }, + 'fortinet.firewall.encrypt': { + category: 'fortinet', + description: 'Whether the packet is encrypted or not ', + name: 'fortinet.firewall.encrypt', + type: 'integer', + }, + 'fortinet.firewall.encryption': { + category: 'fortinet', + description: 'Encryption method ', + name: 'fortinet.firewall.encryption', + type: 'keyword', + }, + 'fortinet.firewall.epoch': { + category: 'fortinet', + description: 'Epoch used for locating file ', + name: 'fortinet.firewall.epoch', + type: 'integer', + }, + 'fortinet.firewall.espauth': { + category: 'fortinet', + description: 'ESP Authentication ', + name: 'fortinet.firewall.espauth', + type: 'keyword', + }, + 'fortinet.firewall.esptransform': { + category: 'fortinet', + description: 'ESP Transform ', + name: 'fortinet.firewall.esptransform', + type: 'keyword', + }, + 'fortinet.firewall.exch': { + category: 'fortinet', + description: 'Mail Exchanges from DNS response answer section ', + name: 'fortinet.firewall.exch', + type: 'keyword', + }, + 'fortinet.firewall.exchange': { + category: 'fortinet', + description: 'Mail Exchanges from DNS response answer section ', + name: 'fortinet.firewall.exchange', + type: 'keyword', + }, + 'fortinet.firewall.expectedsignature': { + category: 'fortinet', + description: 'Expected SSL signature ', + name: 'fortinet.firewall.expectedsignature', + type: 'keyword', + }, + 'fortinet.firewall.expiry': { + category: 'fortinet', + description: 'FortiGuard override expiry timestamp ', + name: 'fortinet.firewall.expiry', + type: 'keyword', + }, + 'fortinet.firewall.fams_pause': { + category: 'fortinet', + description: 'Fortinet Analysis and Management Service Pause ', + name: 'fortinet.firewall.fams_pause', + type: 'integer', + }, + 'fortinet.firewall.fazlograte': { + category: 'fortinet', + description: 'FortiAnalyzer Logging Rate ', + name: 'fortinet.firewall.fazlograte', + type: 'long', + }, + 'fortinet.firewall.fctemssn': { + category: 'fortinet', + description: 'FortiClient Endpoint SSN ', + name: 'fortinet.firewall.fctemssn', + type: 'keyword', + }, + 'fortinet.firewall.fctuid': { + category: 'fortinet', + description: 'FortiClient UID ', + name: 'fortinet.firewall.fctuid', + type: 'keyword', + }, + 'fortinet.firewall.field': { + category: 'fortinet', + description: 'NTP status field ', + name: 'fortinet.firewall.field', + type: 'keyword', + }, + 'fortinet.firewall.filefilter': { + category: 'fortinet', + description: 'The filter used to identify the affected file ', + name: 'fortinet.firewall.filefilter', + type: 'keyword', + }, + 'fortinet.firewall.filehashsrc': { + category: 'fortinet', + description: 'Filehash source ', + name: 'fortinet.firewall.filehashsrc', + type: 'keyword', + }, + 'fortinet.firewall.filtercat': { + category: 'fortinet', + description: 'DLP filter category ', + name: 'fortinet.firewall.filtercat', + type: 'keyword', + }, + 'fortinet.firewall.filteridx': { + category: 'fortinet', + description: 'DLP filter ID ', + name: 'fortinet.firewall.filteridx', + type: 'integer', + }, + 'fortinet.firewall.filtername': { + category: 'fortinet', + description: 'DLP rule name ', + name: 'fortinet.firewall.filtername', + type: 'keyword', + }, + 'fortinet.firewall.filtertype': { + category: 'fortinet', + description: 'DLP filter type ', + name: 'fortinet.firewall.filtertype', + type: 'keyword', + }, + 'fortinet.firewall.fortiguardresp': { + category: 'fortinet', + description: 'Antispam ESP value ', + name: 'fortinet.firewall.fortiguardresp', + type: 'keyword', + }, + 'fortinet.firewall.forwardedfor': { + category: 'fortinet', + description: 'Email address forwarded ', + name: 'fortinet.firewall.forwardedfor', + type: 'keyword', + }, + 'fortinet.firewall.fqdn': { + category: 'fortinet', + description: 'FQDN ', + name: 'fortinet.firewall.fqdn', + type: 'keyword', + }, + 'fortinet.firewall.frametype': { + category: 'fortinet', + description: 'Wireless frametype ', + name: 'fortinet.firewall.frametype', + type: 'keyword', + }, + 'fortinet.firewall.freediskstorage': { + category: 'fortinet', + description: 'Free disk integer ', + name: 'fortinet.firewall.freediskstorage', + type: 'integer', + }, + 'fortinet.firewall.from': { + category: 'fortinet', + description: 'From email address ', + name: 'fortinet.firewall.from', + type: 'keyword', + }, + 'fortinet.firewall.from_vcluster': { + category: 'fortinet', + description: 'Source virtual cluster number ', + name: 'fortinet.firewall.from_vcluster', + type: 'integer', + }, + 'fortinet.firewall.fsaverdict': { + category: 'fortinet', + description: 'FSA verdict ', + name: 'fortinet.firewall.fsaverdict', + type: 'keyword', + }, + 'fortinet.firewall.fwserver_name': { + category: 'fortinet', + description: 'Web proxy server name ', + name: 'fortinet.firewall.fwserver_name', + type: 'keyword', + }, + 'fortinet.firewall.gateway': { + category: 'fortinet', + description: 'Gateway ip address for PPPoE status report ', + name: 'fortinet.firewall.gateway', + type: 'ip', + }, + 'fortinet.firewall.green': { + category: 'fortinet', + description: 'Memory status ', + name: 'fortinet.firewall.green', + type: 'keyword', + }, + 'fortinet.firewall.groupid': { + category: 'fortinet', + description: 'User Group ID ', + name: 'fortinet.firewall.groupid', + type: 'integer', + }, + 'fortinet.firewall.ha-prio': { + category: 'fortinet', + description: 'HA Priority ', + name: 'fortinet.firewall.ha-prio', + type: 'integer', + }, + 'fortinet.firewall.ha_group': { + category: 'fortinet', + description: 'HA Group ', + name: 'fortinet.firewall.ha_group', + type: 'keyword', + }, + 'fortinet.firewall.ha_role': { + category: 'fortinet', + description: 'HA Role ', + name: 'fortinet.firewall.ha_role', + type: 'keyword', + }, + 'fortinet.firewall.handshake': { + category: 'fortinet', + description: 'SSL Handshake ', + name: 'fortinet.firewall.handshake', + type: 'keyword', + }, + 'fortinet.firewall.hash': { + category: 'fortinet', + description: 'Hash value of downloaded file ', + name: 'fortinet.firewall.hash', + type: 'keyword', + }, + 'fortinet.firewall.hbdn_reason': { + category: 'fortinet', + description: 'Heartbeat down reason ', + name: 'fortinet.firewall.hbdn_reason', + type: 'keyword', + }, + 'fortinet.firewall.highcount': { + category: 'fortinet', + description: 'Highcount fabric summary ', + name: 'fortinet.firewall.highcount', + type: 'integer', + }, + 'fortinet.firewall.host': { + category: 'fortinet', + description: 'Hostname ', + name: 'fortinet.firewall.host', + type: 'keyword', + }, + 'fortinet.firewall.iaid': { + category: 'fortinet', + description: 'DHCPv6 id ', + name: 'fortinet.firewall.iaid', + type: 'keyword', + }, + 'fortinet.firewall.icmpcode': { + category: 'fortinet', + description: 'Destination Port of the ICMP message ', + name: 'fortinet.firewall.icmpcode', + type: 'keyword', + }, + 'fortinet.firewall.icmpid': { + category: 'fortinet', + description: 'Source port of the ICMP message ', + name: 'fortinet.firewall.icmpid', + type: 'keyword', + }, + 'fortinet.firewall.icmptype': { + category: 'fortinet', + description: 'The type of ICMP message ', + name: 'fortinet.firewall.icmptype', + type: 'keyword', + }, + 'fortinet.firewall.identifier': { + category: 'fortinet', + description: 'Network traffic identifier ', + name: 'fortinet.firewall.identifier', + type: 'integer', + }, + 'fortinet.firewall.in_spi': { + category: 'fortinet', + description: 'IPSEC inbound SPI ', + name: 'fortinet.firewall.in_spi', + type: 'keyword', + }, + 'fortinet.firewall.incidentserialno': { + category: 'fortinet', + description: 'Incident serial number ', + name: 'fortinet.firewall.incidentserialno', + type: 'integer', + }, + 'fortinet.firewall.infected': { + category: 'fortinet', + description: 'Infected MMS ', + name: 'fortinet.firewall.infected', + type: 'integer', + }, + 'fortinet.firewall.infectedfilelevel': { + category: 'fortinet', + description: 'DLP infected file level ', + name: 'fortinet.firewall.infectedfilelevel', + type: 'integer', + }, + 'fortinet.firewall.informationsource': { + category: 'fortinet', + description: 'Information source ', + name: 'fortinet.firewall.informationsource', + type: 'keyword', + }, + 'fortinet.firewall.init': { + category: 'fortinet', + description: 'IPSEC init stage ', + name: 'fortinet.firewall.init', + type: 'keyword', + }, + 'fortinet.firewall.initiator': { + category: 'fortinet', + description: 'Original login user name for Fortiguard override ', + name: 'fortinet.firewall.initiator', + type: 'keyword', + }, + 'fortinet.firewall.interface': { + category: 'fortinet', + description: 'Related interface ', + name: 'fortinet.firewall.interface', + type: 'keyword', + }, + 'fortinet.firewall.intf': { + category: 'fortinet', + description: 'Related interface ', + name: 'fortinet.firewall.intf', + type: 'keyword', + }, + 'fortinet.firewall.invalidmac': { + category: 'fortinet', + description: 'The MAC address with invalid OUI ', + name: 'fortinet.firewall.invalidmac', + type: 'keyword', + }, + 'fortinet.firewall.ip': { + category: 'fortinet', + description: 'Related IP ', + name: 'fortinet.firewall.ip', + type: 'ip', + }, + 'fortinet.firewall.iptype': { + category: 'fortinet', + description: 'Related IP type ', + name: 'fortinet.firewall.iptype', + type: 'keyword', + }, + 'fortinet.firewall.keyword': { + category: 'fortinet', + description: 'Keyword used for search ', + name: 'fortinet.firewall.keyword', + type: 'keyword', + }, + 'fortinet.firewall.kind': { + category: 'fortinet', + description: 'VOIP kind ', + name: 'fortinet.firewall.kind', + type: 'keyword', + }, + 'fortinet.firewall.lanin': { + category: 'fortinet', + description: 'LAN incoming traffic in bytes ', + name: 'fortinet.firewall.lanin', + type: 'long', + }, + 'fortinet.firewall.lanout': { + category: 'fortinet', + description: 'LAN outbound traffic in bytes ', + name: 'fortinet.firewall.lanout', + type: 'long', + }, + 'fortinet.firewall.lease': { + category: 'fortinet', + description: 'DHCP lease ', + name: 'fortinet.firewall.lease', + type: 'integer', + }, + 'fortinet.firewall.license_limit': { + category: 'fortinet', + description: 'Maximum Number of FortiClients for the License ', + name: 'fortinet.firewall.license_limit', + type: 'keyword', + }, + 'fortinet.firewall.limit': { + category: 'fortinet', + description: 'Virtual Domain Resource Limit ', + name: 'fortinet.firewall.limit', + type: 'integer', + }, + 'fortinet.firewall.line': { + category: 'fortinet', + description: 'VOIP line ', + name: 'fortinet.firewall.line', + type: 'keyword', + }, + 'fortinet.firewall.live': { + category: 'fortinet', + description: 'Time in seconds ', + name: 'fortinet.firewall.live', + type: 'integer', + }, + 'fortinet.firewall.local': { + category: 'fortinet', + description: 'Local IP for a PPPD Connection ', + name: 'fortinet.firewall.local', + type: 'ip', + }, + 'fortinet.firewall.log': { + category: 'fortinet', + description: 'Log message ', + name: 'fortinet.firewall.log', + type: 'keyword', + }, + 'fortinet.firewall.login': { + category: 'fortinet', + description: 'SSH login ', + name: 'fortinet.firewall.login', + type: 'keyword', + }, + 'fortinet.firewall.lowcount': { + category: 'fortinet', + description: 'Fabric lowcount ', + name: 'fortinet.firewall.lowcount', + type: 'integer', + }, + 'fortinet.firewall.mac': { + category: 'fortinet', + description: 'DHCP mac address ', + name: 'fortinet.firewall.mac', + type: 'keyword', + }, + 'fortinet.firewall.malform_data': { + category: 'fortinet', + description: 'VOIP malformed data ', + name: 'fortinet.firewall.malform_data', + type: 'integer', + }, + 'fortinet.firewall.malform_desc': { + category: 'fortinet', + description: 'VOIP malformed data description ', + name: 'fortinet.firewall.malform_desc', + type: 'keyword', + }, + 'fortinet.firewall.manuf': { + category: 'fortinet', + description: 'Manufacturer name ', + name: 'fortinet.firewall.manuf', + type: 'keyword', + }, + 'fortinet.firewall.masterdstmac': { + category: 'fortinet', + description: 'Master mac address for a host with multiple network interfaces ', + name: 'fortinet.firewall.masterdstmac', + type: 'keyword', + }, + 'fortinet.firewall.mastersrcmac': { + category: 'fortinet', + description: 'The master MAC address for a host that has multiple network interfaces ', + name: 'fortinet.firewall.mastersrcmac', + type: 'keyword', + }, + 'fortinet.firewall.mediumcount': { + category: 'fortinet', + description: 'Fabric medium count ', + name: 'fortinet.firewall.mediumcount', + type: 'integer', + }, + 'fortinet.firewall.mem': { + category: 'fortinet', + description: 'Memory usage system statistics ', + name: 'fortinet.firewall.mem', + type: 'keyword', + }, + 'fortinet.firewall.meshmode': { + category: 'fortinet', + description: 'Wireless mesh mode ', + name: 'fortinet.firewall.meshmode', + type: 'keyword', + }, + 'fortinet.firewall.message_type': { + category: 'fortinet', + description: 'VOIP message type ', + name: 'fortinet.firewall.message_type', + type: 'keyword', + }, + 'fortinet.firewall.method': { + category: 'fortinet', + description: 'HTTP method ', + name: 'fortinet.firewall.method', + type: 'keyword', + }, + 'fortinet.firewall.mgmtcnt': { + category: 'fortinet', + description: 'The number of unauthorized client flooding managemet frames ', + name: 'fortinet.firewall.mgmtcnt', + type: 'integer', + }, + 'fortinet.firewall.mode': { + category: 'fortinet', + description: 'IPSEC mode ', + name: 'fortinet.firewall.mode', + type: 'keyword', + }, + 'fortinet.firewall.module': { + category: 'fortinet', + description: 'PCI-DSS module ', + name: 'fortinet.firewall.module', + type: 'keyword', + }, + 'fortinet.firewall.monitor-name': { + category: 'fortinet', + description: 'Health Monitor Name ', + name: 'fortinet.firewall.monitor-name', + type: 'keyword', + }, + 'fortinet.firewall.monitor-type': { + category: 'fortinet', + description: 'Health Monitor Type ', + name: 'fortinet.firewall.monitor-type', + type: 'keyword', + }, + 'fortinet.firewall.mpsk': { + category: 'fortinet', + description: 'Wireless MPSK ', + name: 'fortinet.firewall.mpsk', + type: 'keyword', + }, + 'fortinet.firewall.msgproto': { + category: 'fortinet', + description: 'Message Protocol Number ', + name: 'fortinet.firewall.msgproto', + type: 'keyword', + }, + 'fortinet.firewall.mtu': { + category: 'fortinet', + description: 'Max Transmission Unit Value ', + name: 'fortinet.firewall.mtu', + type: 'integer', + }, + 'fortinet.firewall.name': { + category: 'fortinet', + description: 'Name ', + name: 'fortinet.firewall.name', + type: 'keyword', + }, + 'fortinet.firewall.nat': { + category: 'fortinet', + description: 'NAT IP Address ', + name: 'fortinet.firewall.nat', + type: 'keyword', + }, + 'fortinet.firewall.netid': { + category: 'fortinet', + description: 'Connector NetID ', + name: 'fortinet.firewall.netid', + type: 'keyword', + }, + 'fortinet.firewall.new_status': { + category: 'fortinet', + description: 'New status on user change ', + name: 'fortinet.firewall.new_status', + type: 'keyword', + }, + 'fortinet.firewall.new_value': { + category: 'fortinet', + description: 'New Virtual Domain Name ', + name: 'fortinet.firewall.new_value', + type: 'keyword', + }, + 'fortinet.firewall.newchannel': { + category: 'fortinet', + description: 'New Channel Number ', + name: 'fortinet.firewall.newchannel', + type: 'integer', + }, + 'fortinet.firewall.newchassisid': { + category: 'fortinet', + description: 'New Chassis ID ', + name: 'fortinet.firewall.newchassisid', + type: 'integer', + }, + 'fortinet.firewall.newslot': { + category: 'fortinet', + description: 'New Slot Number ', + name: 'fortinet.firewall.newslot', + type: 'integer', + }, + 'fortinet.firewall.nextstat': { + category: 'fortinet', + description: 'Time interval in seconds for the next statistics. ', + name: 'fortinet.firewall.nextstat', + type: 'integer', + }, + 'fortinet.firewall.nf_type': { + category: 'fortinet', + description: 'Notification Type ', + name: 'fortinet.firewall.nf_type', + type: 'keyword', + }, + 'fortinet.firewall.noise': { + category: 'fortinet', + description: 'Wifi Noise ', + name: 'fortinet.firewall.noise', + type: 'integer', + }, + 'fortinet.firewall.old_status': { + category: 'fortinet', + description: 'Original Status ', + name: 'fortinet.firewall.old_status', + type: 'keyword', + }, + 'fortinet.firewall.old_value': { + category: 'fortinet', + description: 'Original Virtual Domain name ', + name: 'fortinet.firewall.old_value', + type: 'keyword', + }, + 'fortinet.firewall.oldchannel': { + category: 'fortinet', + description: 'Original channel ', + name: 'fortinet.firewall.oldchannel', + type: 'integer', + }, + 'fortinet.firewall.oldchassisid': { + category: 'fortinet', + description: 'Original Chassis Number ', + name: 'fortinet.firewall.oldchassisid', + type: 'integer', + }, + 'fortinet.firewall.oldslot': { + category: 'fortinet', + description: 'Original Slot Number ', + name: 'fortinet.firewall.oldslot', + type: 'integer', + }, + 'fortinet.firewall.oldsn': { + category: 'fortinet', + description: 'Old Serial number ', + name: 'fortinet.firewall.oldsn', + type: 'keyword', + }, + 'fortinet.firewall.oldwprof': { + category: 'fortinet', + description: 'Old Web Filter Profile ', + name: 'fortinet.firewall.oldwprof', + type: 'keyword', + }, + 'fortinet.firewall.onwire': { + category: 'fortinet', + description: 'A flag to indicate if the AP is onwire or not ', + name: 'fortinet.firewall.onwire', + type: 'keyword', + }, + 'fortinet.firewall.opercountry': { + category: 'fortinet', + description: 'Operating Country ', + name: 'fortinet.firewall.opercountry', + type: 'keyword', + }, + 'fortinet.firewall.opertxpower': { + category: 'fortinet', + description: 'Operating TX power ', + name: 'fortinet.firewall.opertxpower', + type: 'integer', + }, + 'fortinet.firewall.osname': { + category: 'fortinet', + description: 'Operating System name ', + name: 'fortinet.firewall.osname', + type: 'keyword', + }, + 'fortinet.firewall.osversion': { + category: 'fortinet', + description: 'Operating System version ', + name: 'fortinet.firewall.osversion', + type: 'keyword', + }, + 'fortinet.firewall.out_spi': { + category: 'fortinet', + description: 'Out SPI ', + name: 'fortinet.firewall.out_spi', + type: 'keyword', + }, + 'fortinet.firewall.outintf': { + category: 'fortinet', + description: 'Out interface ', + name: 'fortinet.firewall.outintf', + type: 'keyword', + }, + 'fortinet.firewall.passedcount': { + category: 'fortinet', + description: 'Fabric passed count ', + name: 'fortinet.firewall.passedcount', + type: 'integer', + }, + 'fortinet.firewall.passwd': { + category: 'fortinet', + description: 'Changed user password information ', + name: 'fortinet.firewall.passwd', + type: 'keyword', + }, + 'fortinet.firewall.path': { + category: 'fortinet', + description: 'Path of looped configuration for security fabric ', + name: 'fortinet.firewall.path', + type: 'keyword', + }, + 'fortinet.firewall.peer': { + category: 'fortinet', + description: 'WAN optimization peer ', + name: 'fortinet.firewall.peer', + type: 'keyword', + }, + 'fortinet.firewall.peer_notif': { + category: 'fortinet', + description: 'VPN peer notification ', + name: 'fortinet.firewall.peer_notif', + type: 'keyword', + }, + 'fortinet.firewall.phase2_name': { + category: 'fortinet', + description: 'VPN phase2 name ', + name: 'fortinet.firewall.phase2_name', + type: 'keyword', + }, + 'fortinet.firewall.phone': { + category: 'fortinet', + description: 'VOIP Phone ', + name: 'fortinet.firewall.phone', + type: 'keyword', + }, + 'fortinet.firewall.pid': { + category: 'fortinet', + description: 'Process ID ', + name: 'fortinet.firewall.pid', + type: 'integer', + }, + 'fortinet.firewall.policytype': { + category: 'fortinet', + description: 'Policy Type ', + name: 'fortinet.firewall.policytype', + type: 'keyword', + }, + 'fortinet.firewall.poolname': { + category: 'fortinet', + description: 'IP Pool name ', + name: 'fortinet.firewall.poolname', + type: 'keyword', + }, + 'fortinet.firewall.port': { + category: 'fortinet', + description: 'Log upload error port ', + name: 'fortinet.firewall.port', + type: 'integer', + }, + 'fortinet.firewall.portbegin': { + category: 'fortinet', + description: 'IP Pool port number to begin ', + name: 'fortinet.firewall.portbegin', + type: 'integer', + }, + 'fortinet.firewall.portend': { + category: 'fortinet', + description: 'IP Pool port number to end ', + name: 'fortinet.firewall.portend', + type: 'integer', + }, + 'fortinet.firewall.probeproto': { + category: 'fortinet', + description: 'Link Monitor Probe Protocol ', + name: 'fortinet.firewall.probeproto', + type: 'keyword', + }, + 'fortinet.firewall.process': { + category: 'fortinet', + description: 'URL Filter process ', + name: 'fortinet.firewall.process', + type: 'keyword', + }, + 'fortinet.firewall.processtime': { + category: 'fortinet', + description: 'Process time for reports ', + name: 'fortinet.firewall.processtime', + type: 'integer', + }, + 'fortinet.firewall.profile': { + category: 'fortinet', + description: 'Profile Name ', + name: 'fortinet.firewall.profile', + type: 'keyword', + }, + 'fortinet.firewall.profile_vd': { + category: 'fortinet', + description: 'Virtual Domain Name ', + name: 'fortinet.firewall.profile_vd', + type: 'keyword', + }, + 'fortinet.firewall.profilegroup': { + category: 'fortinet', + description: 'Profile Group Name ', + name: 'fortinet.firewall.profilegroup', + type: 'keyword', + }, + 'fortinet.firewall.profiletype': { + category: 'fortinet', + description: 'Profile Type ', + name: 'fortinet.firewall.profiletype', + type: 'keyword', + }, + 'fortinet.firewall.qtypeval': { + category: 'fortinet', + description: 'DNS question type value ', + name: 'fortinet.firewall.qtypeval', + type: 'integer', + }, + 'fortinet.firewall.quarskip': { + category: 'fortinet', + description: 'Quarantine skip explanation ', + name: 'fortinet.firewall.quarskip', + type: 'keyword', + }, + 'fortinet.firewall.quotaexceeded': { + category: 'fortinet', + description: 'If quota has been exceeded ', + name: 'fortinet.firewall.quotaexceeded', + type: 'keyword', + }, + 'fortinet.firewall.quotamax': { + category: 'fortinet', + description: 'Maximum quota allowed - in seconds if time-based - in bytes if traffic-based ', + name: 'fortinet.firewall.quotamax', + type: 'long', + }, + 'fortinet.firewall.quotatype': { + category: 'fortinet', + description: 'Quota type ', + name: 'fortinet.firewall.quotatype', + type: 'keyword', + }, + 'fortinet.firewall.quotaused': { + category: 'fortinet', + description: 'Quota used - in seconds if time-based - in bytes if trafficbased) ', + name: 'fortinet.firewall.quotaused', + type: 'long', + }, + 'fortinet.firewall.radioband': { + category: 'fortinet', + description: 'Radio band ', + name: 'fortinet.firewall.radioband', + type: 'keyword', + }, + 'fortinet.firewall.radioid': { + category: 'fortinet', + description: 'Radio ID ', + name: 'fortinet.firewall.radioid', + type: 'integer', + }, + 'fortinet.firewall.radioidclosest': { + category: 'fortinet', + description: 'Radio ID on the AP closest the rogue AP ', + name: 'fortinet.firewall.radioidclosest', + type: 'integer', + }, + 'fortinet.firewall.radioiddetected': { + category: 'fortinet', + description: 'Radio ID on the AP which detected the rogue AP ', + name: 'fortinet.firewall.radioiddetected', + type: 'integer', + }, + 'fortinet.firewall.rate': { + category: 'fortinet', + description: 'Wireless rogue rate value ', + name: 'fortinet.firewall.rate', + type: 'keyword', + }, + 'fortinet.firewall.rawdata': { + category: 'fortinet', + description: 'Raw data value ', + name: 'fortinet.firewall.rawdata', + type: 'keyword', + }, + 'fortinet.firewall.rawdataid': { + category: 'fortinet', + description: 'Raw data ID ', + name: 'fortinet.firewall.rawdataid', + type: 'keyword', + }, + 'fortinet.firewall.rcvddelta': { + category: 'fortinet', + description: 'Received bytes delta ', + name: 'fortinet.firewall.rcvddelta', + type: 'keyword', + }, + 'fortinet.firewall.reason': { + category: 'fortinet', + description: 'Alert reason ', + name: 'fortinet.firewall.reason', + type: 'keyword', + }, + 'fortinet.firewall.received': { + category: 'fortinet', + description: 'Server key exchange received ', + name: 'fortinet.firewall.received', + type: 'integer', + }, + 'fortinet.firewall.receivedsignature': { + category: 'fortinet', + description: 'Server key exchange received signature ', + name: 'fortinet.firewall.receivedsignature', + type: 'keyword', + }, + 'fortinet.firewall.red': { + category: 'fortinet', + description: 'Memory information in red ', + name: 'fortinet.firewall.red', + type: 'keyword', + }, + 'fortinet.firewall.referralurl': { + category: 'fortinet', + description: 'Web filter referralurl ', + name: 'fortinet.firewall.referralurl', + type: 'keyword', + }, + 'fortinet.firewall.remote': { + category: 'fortinet', + description: 'Remote PPP IP address ', + name: 'fortinet.firewall.remote', + type: 'ip', + }, + 'fortinet.firewall.remotewtptime': { + category: 'fortinet', + description: 'Remote Wifi Radius authentication time ', + name: 'fortinet.firewall.remotewtptime', + type: 'keyword', + }, + 'fortinet.firewall.reporttype': { + category: 'fortinet', + description: 'Report type ', + name: 'fortinet.firewall.reporttype', + type: 'keyword', + }, + 'fortinet.firewall.reqtype': { + category: 'fortinet', + description: 'Request type ', + name: 'fortinet.firewall.reqtype', + type: 'keyword', + }, + 'fortinet.firewall.request_name': { + category: 'fortinet', + description: 'VOIP request name ', + name: 'fortinet.firewall.request_name', + type: 'keyword', + }, + 'fortinet.firewall.result': { + category: 'fortinet', + description: 'VPN phase result ', + name: 'fortinet.firewall.result', + type: 'keyword', + }, + 'fortinet.firewall.role': { + category: 'fortinet', + description: 'VPN Phase 2 role ', + name: 'fortinet.firewall.role', + type: 'keyword', + }, + 'fortinet.firewall.rssi': { + category: 'fortinet', + description: 'Received signal strength indicator ', + name: 'fortinet.firewall.rssi', + type: 'integer', + }, + 'fortinet.firewall.rsso_key': { + category: 'fortinet', + description: 'RADIUS SSO attribute value ', + name: 'fortinet.firewall.rsso_key', + type: 'keyword', + }, + 'fortinet.firewall.ruledata': { + category: 'fortinet', + description: 'Rule data ', + name: 'fortinet.firewall.ruledata', + type: 'keyword', + }, + 'fortinet.firewall.ruletype': { + category: 'fortinet', + description: 'Rule type ', + name: 'fortinet.firewall.ruletype', + type: 'keyword', + }, + 'fortinet.firewall.scanned': { + category: 'fortinet', + description: 'Number of Scanned MMSs ', + name: 'fortinet.firewall.scanned', + type: 'integer', + }, + 'fortinet.firewall.scantime': { + category: 'fortinet', + description: 'Scanned time ', + name: 'fortinet.firewall.scantime', + type: 'long', + }, + 'fortinet.firewall.scope': { + category: 'fortinet', + description: 'FortiGuard Override Scope ', + name: 'fortinet.firewall.scope', + type: 'keyword', + }, + 'fortinet.firewall.security': { + category: 'fortinet', + description: 'Wireless rogue security ', + name: 'fortinet.firewall.security', + type: 'keyword', + }, + 'fortinet.firewall.sensitivity': { + category: 'fortinet', + description: 'Sensitivity for document fingerprint ', + name: 'fortinet.firewall.sensitivity', + type: 'keyword', + }, + 'fortinet.firewall.sensor': { + category: 'fortinet', + description: 'NAC Sensor Name ', + name: 'fortinet.firewall.sensor', + type: 'keyword', + }, + 'fortinet.firewall.sentdelta': { + category: 'fortinet', + description: 'Sent bytes delta ', + name: 'fortinet.firewall.sentdelta', + type: 'keyword', + }, + 'fortinet.firewall.seq': { + category: 'fortinet', + description: 'Sequence number ', + name: 'fortinet.firewall.seq', + type: 'keyword', + }, + 'fortinet.firewall.serial': { + category: 'fortinet', + description: 'WAN optimisation serial ', + name: 'fortinet.firewall.serial', + type: 'keyword', + }, + 'fortinet.firewall.serialno': { + category: 'fortinet', + description: 'Serial number ', + name: 'fortinet.firewall.serialno', + type: 'keyword', + }, + 'fortinet.firewall.server': { + category: 'fortinet', + description: 'AD server FQDN or IP ', + name: 'fortinet.firewall.server', + type: 'keyword', + }, + 'fortinet.firewall.session_id': { + category: 'fortinet', + description: 'Session ID ', + name: 'fortinet.firewall.session_id', + type: 'keyword', + }, + 'fortinet.firewall.sessionid': { + category: 'fortinet', + description: 'WAD Session ID ', + name: 'fortinet.firewall.sessionid', + type: 'integer', + }, + 'fortinet.firewall.setuprate': { + category: 'fortinet', + description: 'Session Setup Rate ', + name: 'fortinet.firewall.setuprate', + type: 'long', + }, + 'fortinet.firewall.severity': { + category: 'fortinet', + description: 'Severity ', + name: 'fortinet.firewall.severity', + type: 'keyword', + }, + 'fortinet.firewall.shaperdroprcvdbyte': { + category: 'fortinet', + description: 'Received bytes dropped by shaper ', + name: 'fortinet.firewall.shaperdroprcvdbyte', + type: 'integer', + }, + 'fortinet.firewall.shaperdropsentbyte': { + category: 'fortinet', + description: 'Sent bytes dropped by shaper ', + name: 'fortinet.firewall.shaperdropsentbyte', + type: 'integer', + }, + 'fortinet.firewall.shaperperipdropbyte': { + category: 'fortinet', + description: 'Dropped bytes per IP by shaper ', + name: 'fortinet.firewall.shaperperipdropbyte', + type: 'integer', + }, + 'fortinet.firewall.shaperperipname': { + category: 'fortinet', + description: 'Traffic shaper name (per IP) ', + name: 'fortinet.firewall.shaperperipname', + type: 'keyword', + }, + 'fortinet.firewall.shaperrcvdname': { + category: 'fortinet', + description: 'Traffic shaper name for received traffic ', + name: 'fortinet.firewall.shaperrcvdname', + type: 'keyword', + }, + 'fortinet.firewall.shapersentname': { + category: 'fortinet', + description: 'Traffic shaper name for sent traffic ', + name: 'fortinet.firewall.shapersentname', + type: 'keyword', + }, + 'fortinet.firewall.shapingpolicyid': { + category: 'fortinet', + description: 'Traffic shaper policy ID ', + name: 'fortinet.firewall.shapingpolicyid', + type: 'integer', + }, + 'fortinet.firewall.signal': { + category: 'fortinet', + description: 'Wireless rogue API signal ', + name: 'fortinet.firewall.signal', + type: 'integer', + }, + 'fortinet.firewall.size': { + category: 'fortinet', + description: 'Email size in bytes ', + name: 'fortinet.firewall.size', + type: 'long', + }, + 'fortinet.firewall.slot': { + category: 'fortinet', + description: 'Slot number ', + name: 'fortinet.firewall.slot', + type: 'integer', + }, + 'fortinet.firewall.sn': { + category: 'fortinet', + description: 'Security fabric serial number ', + name: 'fortinet.firewall.sn', + type: 'keyword', + }, + 'fortinet.firewall.snclosest': { + category: 'fortinet', + description: 'SN of the AP closest to the rogue AP ', + name: 'fortinet.firewall.snclosest', + type: 'keyword', + }, + 'fortinet.firewall.sndetected': { + category: 'fortinet', + description: 'SN of the AP which detected the rogue AP ', + name: 'fortinet.firewall.sndetected', + type: 'keyword', + }, + 'fortinet.firewall.snmeshparent': { + category: 'fortinet', + description: 'SN of the mesh parent ', + name: 'fortinet.firewall.snmeshparent', + type: 'keyword', + }, + 'fortinet.firewall.spi': { + category: 'fortinet', + description: 'IPSEC SPI ', + name: 'fortinet.firewall.spi', + type: 'keyword', + }, + 'fortinet.firewall.src_int': { + category: 'fortinet', + description: 'Source interface ', + name: 'fortinet.firewall.src_int', + type: 'keyword', + }, + 'fortinet.firewall.srcintfrole': { + category: 'fortinet', + description: 'Source interface role ', + name: 'fortinet.firewall.srcintfrole', + type: 'keyword', + }, + 'fortinet.firewall.srccountry': { + category: 'fortinet', + description: 'Source country ', + name: 'fortinet.firewall.srccountry', + type: 'keyword', + }, + 'fortinet.firewall.srcfamily': { + category: 'fortinet', + description: 'Source family ', + name: 'fortinet.firewall.srcfamily', + type: 'keyword', + }, + 'fortinet.firewall.srchwvendor': { + category: 'fortinet', + description: 'Source hardware vendor ', + name: 'fortinet.firewall.srchwvendor', + type: 'keyword', + }, + 'fortinet.firewall.srchwversion': { + category: 'fortinet', + description: 'Source hardware version ', + name: 'fortinet.firewall.srchwversion', + type: 'keyword', + }, + 'fortinet.firewall.srcinetsvc': { + category: 'fortinet', + description: 'Source interface service ', + name: 'fortinet.firewall.srcinetsvc', + type: 'keyword', + }, + 'fortinet.firewall.srcname': { + category: 'fortinet', + description: 'Source name ', + name: 'fortinet.firewall.srcname', + type: 'keyword', + }, + 'fortinet.firewall.srcserver': { + category: 'fortinet', + description: 'Source server ', + name: 'fortinet.firewall.srcserver', + type: 'integer', + }, + 'fortinet.firewall.srcssid': { + category: 'fortinet', + description: 'Source SSID ', + name: 'fortinet.firewall.srcssid', + type: 'keyword', + }, + 'fortinet.firewall.srcswversion': { + category: 'fortinet', + description: 'Source software version ', + name: 'fortinet.firewall.srcswversion', + type: 'keyword', + }, + 'fortinet.firewall.srcuuid': { + category: 'fortinet', + description: 'Source UUID ', + name: 'fortinet.firewall.srcuuid', + type: 'keyword', + }, + 'fortinet.firewall.sscname': { + category: 'fortinet', + description: 'SSC name ', + name: 'fortinet.firewall.sscname', + type: 'keyword', + }, + 'fortinet.firewall.ssid': { + category: 'fortinet', + description: 'Base Service Set ID ', + name: 'fortinet.firewall.ssid', + type: 'keyword', + }, + 'fortinet.firewall.sslaction': { + category: 'fortinet', + description: 'SSL Action ', + name: 'fortinet.firewall.sslaction', + type: 'keyword', + }, + 'fortinet.firewall.ssllocal': { + category: 'fortinet', + description: 'WAD SSL local ', + name: 'fortinet.firewall.ssllocal', + type: 'keyword', + }, + 'fortinet.firewall.sslremote': { + category: 'fortinet', + description: 'WAD SSL remote ', + name: 'fortinet.firewall.sslremote', + type: 'keyword', + }, + 'fortinet.firewall.stacount': { + category: 'fortinet', + description: 'Number of stations/clients ', + name: 'fortinet.firewall.stacount', + type: 'integer', + }, + 'fortinet.firewall.stage': { + category: 'fortinet', + description: 'IPSEC stage ', + name: 'fortinet.firewall.stage', + type: 'keyword', + }, + 'fortinet.firewall.stamac': { + category: 'fortinet', + description: '802.1x station mac ', + name: 'fortinet.firewall.stamac', + type: 'keyword', + }, + 'fortinet.firewall.state': { + category: 'fortinet', + description: 'Admin login state ', + name: 'fortinet.firewall.state', + type: 'keyword', + }, + 'fortinet.firewall.status': { + category: 'fortinet', + description: 'Status ', + name: 'fortinet.firewall.status', + type: 'keyword', + }, + 'fortinet.firewall.stitch': { + category: 'fortinet', + description: 'Automation stitch triggered ', + name: 'fortinet.firewall.stitch', + type: 'keyword', + }, + 'fortinet.firewall.subject': { + category: 'fortinet', + description: 'Email subject ', + name: 'fortinet.firewall.subject', + type: 'keyword', + }, + 'fortinet.firewall.submodule': { + category: 'fortinet', + description: 'Configuration Sub-Module Name ', + name: 'fortinet.firewall.submodule', + type: 'keyword', + }, + 'fortinet.firewall.subservice': { + category: 'fortinet', + description: 'AV subservice ', + name: 'fortinet.firewall.subservice', + type: 'keyword', + }, + 'fortinet.firewall.subtype': { + category: 'fortinet', + description: 'Log subtype ', + name: 'fortinet.firewall.subtype', + type: 'keyword', + }, + 'fortinet.firewall.suspicious': { + category: 'fortinet', + description: 'Number of Suspicious MMSs ', + name: 'fortinet.firewall.suspicious', + type: 'integer', + }, + 'fortinet.firewall.switchproto': { + category: 'fortinet', + description: 'Protocol change information ', + name: 'fortinet.firewall.switchproto', + type: 'keyword', + }, + 'fortinet.firewall.sync_status': { + category: 'fortinet', + description: 'The sync status with the master ', + name: 'fortinet.firewall.sync_status', + type: 'keyword', + }, + 'fortinet.firewall.sync_type': { + category: 'fortinet', + description: 'The sync type with the master ', + name: 'fortinet.firewall.sync_type', + type: 'keyword', + }, + 'fortinet.firewall.sysuptime': { + category: 'fortinet', + description: 'System uptime ', + name: 'fortinet.firewall.sysuptime', + type: 'keyword', + }, + 'fortinet.firewall.tamac': { + category: 'fortinet', + description: 'the MAC address of Transmitter, if none, then Receiver ', + name: 'fortinet.firewall.tamac', + type: 'keyword', + }, + 'fortinet.firewall.threattype': { + category: 'fortinet', + description: 'WIDS threat type ', + name: 'fortinet.firewall.threattype', + type: 'keyword', + }, + 'fortinet.firewall.time': { + category: 'fortinet', + description: 'Time of the event ', + name: 'fortinet.firewall.time', + type: 'keyword', + }, + 'fortinet.firewall.to': { + category: 'fortinet', + description: 'Email to field ', + name: 'fortinet.firewall.to', + type: 'keyword', + }, + 'fortinet.firewall.to_vcluster': { + category: 'fortinet', + description: 'destination virtual cluster number ', + name: 'fortinet.firewall.to_vcluster', + type: 'integer', + }, + 'fortinet.firewall.total': { + category: 'fortinet', + description: 'Total memory ', + name: 'fortinet.firewall.total', + type: 'integer', + }, + 'fortinet.firewall.totalsession': { + category: 'fortinet', + description: 'Total Number of Sessions ', + name: 'fortinet.firewall.totalsession', + type: 'integer', + }, + 'fortinet.firewall.trace_id': { + category: 'fortinet', + description: 'Session clash trace ID ', + name: 'fortinet.firewall.trace_id', + type: 'keyword', + }, + 'fortinet.firewall.trandisp': { + category: 'fortinet', + description: 'NAT translation type ', + name: 'fortinet.firewall.trandisp', + type: 'keyword', + }, + 'fortinet.firewall.transid': { + category: 'fortinet', + description: 'HTTP transaction ID ', + name: 'fortinet.firewall.transid', + type: 'integer', + }, + 'fortinet.firewall.translationid': { + category: 'fortinet', + description: 'DNS filter transaltion ID ', + name: 'fortinet.firewall.translationid', + type: 'keyword', + }, + 'fortinet.firewall.trigger': { + category: 'fortinet', + description: 'Automation stitch trigger ', + name: 'fortinet.firewall.trigger', + type: 'keyword', + }, + 'fortinet.firewall.trueclntip': { + category: 'fortinet', + description: 'File filter true client IP ', + name: 'fortinet.firewall.trueclntip', + type: 'ip', + }, + 'fortinet.firewall.tunnelid': { + category: 'fortinet', + description: 'IPSEC tunnel ID ', + name: 'fortinet.firewall.tunnelid', + type: 'integer', + }, + 'fortinet.firewall.tunnelip': { + category: 'fortinet', + description: 'IPSEC tunnel IP ', + name: 'fortinet.firewall.tunnelip', + type: 'ip', + }, + 'fortinet.firewall.tunneltype': { + category: 'fortinet', + description: 'IPSEC tunnel type ', + name: 'fortinet.firewall.tunneltype', + type: 'keyword', + }, + 'fortinet.firewall.type': { + category: 'fortinet', + description: 'Module type ', + name: 'fortinet.firewall.type', + type: 'keyword', + }, + 'fortinet.firewall.ui': { + category: 'fortinet', + description: 'Admin authentication UI type ', + name: 'fortinet.firewall.ui', + type: 'keyword', + }, + 'fortinet.firewall.unauthusersource': { + category: 'fortinet', + description: 'Unauthenticated user source ', + name: 'fortinet.firewall.unauthusersource', + type: 'keyword', + }, + 'fortinet.firewall.unit': { + category: 'fortinet', + description: 'Power supply unit ', + name: 'fortinet.firewall.unit', + type: 'integer', + }, + 'fortinet.firewall.urlfilteridx': { + category: 'fortinet', + description: 'URL filter ID ', + name: 'fortinet.firewall.urlfilteridx', + type: 'integer', + }, + 'fortinet.firewall.urlfilterlist': { + category: 'fortinet', + description: 'URL filter list ', + name: 'fortinet.firewall.urlfilterlist', + type: 'keyword', + }, + 'fortinet.firewall.urlsource': { + category: 'fortinet', + description: 'URL filter source ', + name: 'fortinet.firewall.urlsource', + type: 'keyword', + }, + 'fortinet.firewall.urltype': { + category: 'fortinet', + description: 'URL filter type ', + name: 'fortinet.firewall.urltype', + type: 'keyword', + }, + 'fortinet.firewall.used': { + category: 'fortinet', + description: 'Number of Used IPs ', + name: 'fortinet.firewall.used', + type: 'integer', + }, + 'fortinet.firewall.used_for_type': { + category: 'fortinet', + description: 'Connection for the type ', + name: 'fortinet.firewall.used_for_type', + type: 'integer', + }, + 'fortinet.firewall.utmaction': { + category: 'fortinet', + description: 'Security action performed by UTM ', + name: 'fortinet.firewall.utmaction', + type: 'keyword', + }, + 'fortinet.firewall.vap': { + category: 'fortinet', + description: 'Virtual AP ', + name: 'fortinet.firewall.vap', + type: 'keyword', + }, + 'fortinet.firewall.vapmode': { + category: 'fortinet', + description: 'Virtual AP mode ', + name: 'fortinet.firewall.vapmode', + type: 'keyword', + }, + 'fortinet.firewall.vcluster': { + category: 'fortinet', + description: 'virtual cluster id ', + name: 'fortinet.firewall.vcluster', + type: 'integer', + }, + 'fortinet.firewall.vcluster_member': { + category: 'fortinet', + description: 'Virtual cluster member ', + name: 'fortinet.firewall.vcluster_member', + type: 'integer', + }, + 'fortinet.firewall.vcluster_state': { + category: 'fortinet', + description: 'Virtual cluster state ', + name: 'fortinet.firewall.vcluster_state', + type: 'keyword', + }, + 'fortinet.firewall.vd': { + category: 'fortinet', + description: 'Virtual Domain Name ', + name: 'fortinet.firewall.vd', + type: 'keyword', + }, + 'fortinet.firewall.vdname': { + category: 'fortinet', + description: 'Virtual Domain Name ', + name: 'fortinet.firewall.vdname', + type: 'keyword', + }, + 'fortinet.firewall.vendorurl': { + category: 'fortinet', + description: 'Vulnerability scan vendor name ', + name: 'fortinet.firewall.vendorurl', + type: 'keyword', + }, + 'fortinet.firewall.version': { + category: 'fortinet', + description: 'Version ', + name: 'fortinet.firewall.version', + type: 'keyword', + }, + 'fortinet.firewall.vip': { + category: 'fortinet', + description: 'Virtual IP ', + name: 'fortinet.firewall.vip', + type: 'keyword', + }, + 'fortinet.firewall.virus': { + category: 'fortinet', + description: 'Virus name ', + name: 'fortinet.firewall.virus', + type: 'keyword', + }, + 'fortinet.firewall.virusid': { + category: 'fortinet', + description: 'Virus ID (unique virus identifier) ', + name: 'fortinet.firewall.virusid', + type: 'integer', + }, + 'fortinet.firewall.voip_proto': { + category: 'fortinet', + description: 'VOIP protocol ', + name: 'fortinet.firewall.voip_proto', + type: 'keyword', + }, + 'fortinet.firewall.vpn': { + category: 'fortinet', + description: 'VPN description ', + name: 'fortinet.firewall.vpn', + type: 'keyword', + }, + 'fortinet.firewall.vpntunnel': { + category: 'fortinet', + description: 'IPsec Vpn Tunnel Name ', + name: 'fortinet.firewall.vpntunnel', + type: 'keyword', + }, + 'fortinet.firewall.vpntype': { + category: 'fortinet', + description: 'The type of the VPN tunnel ', + name: 'fortinet.firewall.vpntype', + type: 'keyword', + }, + 'fortinet.firewall.vrf': { + category: 'fortinet', + description: 'VRF number ', + name: 'fortinet.firewall.vrf', + type: 'integer', + }, + 'fortinet.firewall.vulncat': { + category: 'fortinet', + description: 'Vulnerability Category ', + name: 'fortinet.firewall.vulncat', + type: 'keyword', + }, + 'fortinet.firewall.vulnid': { + category: 'fortinet', + description: 'Vulnerability ID ', + name: 'fortinet.firewall.vulnid', + type: 'integer', + }, + 'fortinet.firewall.vulnname': { + category: 'fortinet', + description: 'Vulnerability name ', + name: 'fortinet.firewall.vulnname', + type: 'keyword', + }, + 'fortinet.firewall.vwlid': { + category: 'fortinet', + description: 'VWL ID ', + name: 'fortinet.firewall.vwlid', + type: 'integer', + }, + 'fortinet.firewall.vwlquality': { + category: 'fortinet', + description: 'VWL quality ', + name: 'fortinet.firewall.vwlquality', + type: 'keyword', + }, + 'fortinet.firewall.vwlservice': { + category: 'fortinet', + description: 'VWL service ', + name: 'fortinet.firewall.vwlservice', + type: 'keyword', + }, + 'fortinet.firewall.vwpvlanid': { + category: 'fortinet', + description: 'VWP VLAN ID ', + name: 'fortinet.firewall.vwpvlanid', + type: 'integer', + }, + 'fortinet.firewall.wanin': { + category: 'fortinet', + description: 'WAN incoming traffic in bytes ', + name: 'fortinet.firewall.wanin', + type: 'long', + }, + 'fortinet.firewall.wanoptapptype': { + category: 'fortinet', + description: 'WAN Optimization Application type ', + name: 'fortinet.firewall.wanoptapptype', + type: 'keyword', + }, + 'fortinet.firewall.wanout': { + category: 'fortinet', + description: 'WAN outgoing traffic in bytes ', + name: 'fortinet.firewall.wanout', + type: 'long', + }, + 'fortinet.firewall.weakwepiv': { + category: 'fortinet', + description: 'Weak Wep Initiation Vector ', + name: 'fortinet.firewall.weakwepiv', + type: 'keyword', + }, + 'fortinet.firewall.xauthgroup': { + category: 'fortinet', + description: 'XAuth Group Name ', + name: 'fortinet.firewall.xauthgroup', + type: 'keyword', + }, + 'fortinet.firewall.xauthuser': { + category: 'fortinet', + description: 'XAuth User Name ', + name: 'fortinet.firewall.xauthuser', + type: 'keyword', + }, + 'fortinet.firewall.xid': { + category: 'fortinet', + description: 'Wireless X ID ', + name: 'fortinet.firewall.xid', + type: 'integer', + }, + 'googlecloud.destination.instance.project_id': { + category: 'googlecloud', + description: 'ID of the project containing the VM. ', + name: 'googlecloud.destination.instance.project_id', + type: 'keyword', + }, + 'googlecloud.destination.instance.region': { + category: 'googlecloud', + description: 'Region of the VM. ', + name: 'googlecloud.destination.instance.region', + type: 'keyword', + }, + 'googlecloud.destination.instance.zone': { + category: 'googlecloud', + description: 'Zone of the VM. ', + name: 'googlecloud.destination.instance.zone', + type: 'keyword', + }, + 'googlecloud.destination.vpc.project_id': { + category: 'googlecloud', + description: 'ID of the project containing the VM. ', + name: 'googlecloud.destination.vpc.project_id', + type: 'keyword', + }, + 'googlecloud.destination.vpc.vpc_name': { + category: 'googlecloud', + description: 'VPC on which the VM is operating. ', + name: 'googlecloud.destination.vpc.vpc_name', + type: 'keyword', + }, + 'googlecloud.destination.vpc.subnetwork_name': { + category: 'googlecloud', + description: 'Subnetwork on which the VM is operating. ', + name: 'googlecloud.destination.vpc.subnetwork_name', + type: 'keyword', + }, + 'googlecloud.source.instance.project_id': { + category: 'googlecloud', + description: 'ID of the project containing the VM. ', + name: 'googlecloud.source.instance.project_id', + type: 'keyword', + }, + 'googlecloud.source.instance.region': { + category: 'googlecloud', + description: 'Region of the VM. ', + name: 'googlecloud.source.instance.region', + type: 'keyword', + }, + 'googlecloud.source.instance.zone': { + category: 'googlecloud', + description: 'Zone of the VM. ', + name: 'googlecloud.source.instance.zone', + type: 'keyword', + }, + 'googlecloud.source.vpc.project_id': { + category: 'googlecloud', + description: 'ID of the project containing the VM. ', + name: 'googlecloud.source.vpc.project_id', + type: 'keyword', + }, + 'googlecloud.source.vpc.vpc_name': { + category: 'googlecloud', + description: 'VPC on which the VM is operating. ', + name: 'googlecloud.source.vpc.vpc_name', + type: 'keyword', + }, + 'googlecloud.source.vpc.subnetwork_name': { + category: 'googlecloud', + description: 'Subnetwork on which the VM is operating. ', + name: 'googlecloud.source.vpc.subnetwork_name', + type: 'keyword', + }, + 'googlecloud.audit.type': { + category: 'googlecloud', + description: 'Type property. ', + name: 'googlecloud.audit.type', + type: 'keyword', + }, + 'googlecloud.audit.authentication_info.principal_email': { + category: 'googlecloud', + description: 'The email address of the authenticated user making the request. ', + name: 'googlecloud.audit.authentication_info.principal_email', + type: 'keyword', + }, + 'googlecloud.audit.authentication_info.authority_selector': { + category: 'googlecloud', + description: + 'The authority selector specified by the requestor, if any. It is not guaranteed that the principal was allowed to use this authority. ', + name: 'googlecloud.audit.authentication_info.authority_selector', + type: 'keyword', + }, + 'googlecloud.audit.authorization_info.permission': { + category: 'googlecloud', + description: 'The required IAM permission. ', + name: 'googlecloud.audit.authorization_info.permission', + type: 'keyword', + }, + 'googlecloud.audit.authorization_info.granted': { + category: 'googlecloud', + description: 'Whether or not authorization for resource and permission was granted. ', + name: 'googlecloud.audit.authorization_info.granted', + type: 'boolean', + }, + 'googlecloud.audit.authorization_info.resource_attributes.service': { + category: 'googlecloud', + description: 'The name of the service. ', + name: 'googlecloud.audit.authorization_info.resource_attributes.service', + type: 'keyword', + }, + 'googlecloud.audit.authorization_info.resource_attributes.name': { + category: 'googlecloud', + description: 'The name of the resource. ', + name: 'googlecloud.audit.authorization_info.resource_attributes.name', + type: 'keyword', + }, + 'googlecloud.audit.authorization_info.resource_attributes.type': { + category: 'googlecloud', + description: 'The type of the resource. ', + name: 'googlecloud.audit.authorization_info.resource_attributes.type', + type: 'keyword', + }, + 'googlecloud.audit.method_name': { + category: 'googlecloud', + description: + "The name of the service method or operation. For API calls, this should be the name of the API method. For example, 'google.datastore.v1.Datastore.RunQuery'. ", + name: 'googlecloud.audit.method_name', + type: 'keyword', + }, + 'googlecloud.audit.num_response_items': { + category: 'googlecloud', + description: 'The number of items returned from a List or Query API method, if applicable. ', + name: 'googlecloud.audit.num_response_items', + type: 'long', + }, + 'googlecloud.audit.request.proto_name': { + category: 'googlecloud', + description: 'Type property of the request. ', + name: 'googlecloud.audit.request.proto_name', + type: 'keyword', + }, + 'googlecloud.audit.request.filter': { + category: 'googlecloud', + description: 'Filter of the request. ', + name: 'googlecloud.audit.request.filter', + type: 'keyword', + }, + 'googlecloud.audit.request.name': { + category: 'googlecloud', + description: 'Name of the request. ', + name: 'googlecloud.audit.request.name', + type: 'keyword', + }, + 'googlecloud.audit.request.resource_name': { + category: 'googlecloud', + description: 'Name of the request resource. ', + name: 'googlecloud.audit.request.resource_name', + type: 'keyword', + }, + 'googlecloud.audit.request_metadata.caller_ip': { + category: 'googlecloud', + description: 'The IP address of the caller. ', + name: 'googlecloud.audit.request_metadata.caller_ip', + type: 'ip', + }, + 'googlecloud.audit.request_metadata.caller_supplied_user_agent': { + category: 'googlecloud', + description: + 'The user agent of the caller. This information is not authenticated and should be treated accordingly. ', + name: 'googlecloud.audit.request_metadata.caller_supplied_user_agent', + type: 'keyword', + }, + 'googlecloud.audit.response.proto_name': { + category: 'googlecloud', + description: 'Type property of the response. ', + name: 'googlecloud.audit.response.proto_name', + type: 'keyword', + }, + 'googlecloud.audit.response.details.group': { + category: 'googlecloud', + description: 'The name of the group. ', + name: 'googlecloud.audit.response.details.group', + type: 'keyword', + }, + 'googlecloud.audit.response.details.kind': { + category: 'googlecloud', + description: 'The kind of the response details. ', + name: 'googlecloud.audit.response.details.kind', + type: 'keyword', + }, + 'googlecloud.audit.response.details.name': { + category: 'googlecloud', + description: 'The name of the response details. ', + name: 'googlecloud.audit.response.details.name', + type: 'keyword', + }, + 'googlecloud.audit.response.details.uid': { + category: 'googlecloud', + description: 'The uid of the response details. ', + name: 'googlecloud.audit.response.details.uid', + type: 'keyword', + }, + 'googlecloud.audit.response.status': { + category: 'googlecloud', + description: 'Status of the response. ', + name: 'googlecloud.audit.response.status', + type: 'keyword', + }, + 'googlecloud.audit.resource_name': { + category: 'googlecloud', + description: + "The resource or collection that is the target of the operation. The name is a scheme-less URI, not including the API service name. For example, 'shelves/SHELF_ID/books'. ", + name: 'googlecloud.audit.resource_name', + type: 'keyword', + }, + 'googlecloud.audit.resource_location.current_locations': { + category: 'googlecloud', + description: 'Current locations of the resource. ', + name: 'googlecloud.audit.resource_location.current_locations', + type: 'keyword', + }, + 'googlecloud.audit.service_name': { + category: 'googlecloud', + description: + 'The name of the API service performing the operation. For example, datastore.googleapis.com. ', + name: 'googlecloud.audit.service_name', + type: 'keyword', + }, + 'googlecloud.audit.status.code': { + category: 'googlecloud', + description: 'The status code, which should be an enum value of google.rpc.Code. ', + name: 'googlecloud.audit.status.code', + type: 'integer', + }, + 'googlecloud.audit.status.message': { + category: 'googlecloud', + description: + 'A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the google.rpc.Status.details field, or localized by the client. ', + name: 'googlecloud.audit.status.message', + type: 'keyword', + }, + 'googlecloud.firewall.rule_details.priority': { + category: 'googlecloud', + description: 'The priority for the firewall rule.', + name: 'googlecloud.firewall.rule_details.priority', + type: 'long', + }, + 'googlecloud.firewall.rule_details.action': { + category: 'googlecloud', + description: 'Action that the rule performs on match.', + name: 'googlecloud.firewall.rule_details.action', + type: 'keyword', + }, + 'googlecloud.firewall.rule_details.direction': { + category: 'googlecloud', + description: 'Direction of traffic that matches this rule.', + name: 'googlecloud.firewall.rule_details.direction', + type: 'keyword', + }, + 'googlecloud.firewall.rule_details.reference': { + category: 'googlecloud', + description: 'Reference to the firewall rule.', + name: 'googlecloud.firewall.rule_details.reference', + type: 'keyword', + }, + 'googlecloud.firewall.rule_details.source_range': { + category: 'googlecloud', + description: 'List of source ranges that the firewall rule applies to.', + name: 'googlecloud.firewall.rule_details.source_range', + type: 'keyword', + }, + 'googlecloud.firewall.rule_details.destination_range': { + category: 'googlecloud', + description: 'List of destination ranges that the firewall applies to.', + name: 'googlecloud.firewall.rule_details.destination_range', + type: 'keyword', + }, + 'googlecloud.firewall.rule_details.source_tag': { + category: 'googlecloud', + description: 'List of all the source tags that the firewall rule applies to. ', + name: 'googlecloud.firewall.rule_details.source_tag', + type: 'keyword', + }, + 'googlecloud.firewall.rule_details.target_tag': { + category: 'googlecloud', + description: 'List of all the target tags that the firewall rule applies to. ', + name: 'googlecloud.firewall.rule_details.target_tag', + type: 'keyword', + }, + 'googlecloud.firewall.rule_details.ip_port_info': { + category: 'googlecloud', + description: 'List of ip protocols and applicable port ranges for rules. ', + name: 'googlecloud.firewall.rule_details.ip_port_info', + type: 'array', + }, + 'googlecloud.firewall.rule_details.source_service_account': { + category: 'googlecloud', + description: 'List of all the source service accounts that the firewall rule applies to. ', + name: 'googlecloud.firewall.rule_details.source_service_account', + type: 'keyword', + }, + 'googlecloud.firewall.rule_details.target_service_account': { + category: 'googlecloud', + description: 'List of all the target service accounts that the firewall rule applies to. ', + name: 'googlecloud.firewall.rule_details.target_service_account', + type: 'keyword', + }, + 'googlecloud.vpcflow.reporter': { + category: 'googlecloud', + description: "The side which reported the flow. Can be either 'SRC' or 'DEST'. ", + name: 'googlecloud.vpcflow.reporter', + type: 'keyword', + }, + 'googlecloud.vpcflow.rtt.ms': { + category: 'googlecloud', + description: + 'Latency as measured (for TCP flows only) during the time interval. This is the time elapsed between sending a SEQ and receiving a corresponding ACK and it contains the network RTT as well as the application related delay. ', + name: 'googlecloud.vpcflow.rtt.ms', + type: 'long', + }, + 'gsuite.actor.type': { + category: 'gsuite', + description: + 'The type of actor. Values can be: *USER*: Another user in the same domain. *EXTERNAL_USER*: A user outside the domain. *KEY*: A non-human actor. ', + name: 'gsuite.actor.type', + type: 'keyword', + }, + 'gsuite.actor.key': { + category: 'gsuite', + description: + 'Only present when `actor.type` is `KEY`. Can be the `consumer_key` of the requestor for OAuth 2LO API requests or an identifier for robot accounts. ', + name: 'gsuite.actor.key', + type: 'keyword', + }, + 'gsuite.event.type': { + category: 'gsuite', + description: + 'The type of GSuite event, mapped from `items[].events[].type` in the original payload. Each fileset can have a different set of values for it, more details can be found at https://developers.google.com/admin-sdk/reports/v1/reference/activities/list ', + example: 'audit#activity', + name: 'gsuite.event.type', + type: 'keyword', + }, + 'gsuite.kind': { + category: 'gsuite', + description: + 'The type of API resource, mapped from `kind` in the original payload. More details can be found at https://developers.google.com/admin-sdk/reports/v1/reference/activities/list ', + example: 'audit#activity', + name: 'gsuite.kind', + type: 'keyword', + }, + 'gsuite.organization.domain': { + category: 'gsuite', + description: "The domain that is affected by the report's event. ", + name: 'gsuite.organization.domain', + type: 'keyword', + }, + 'gsuite.admin.application.edition': { + category: 'gsuite', + description: 'The GSuite edition.', + name: 'gsuite.admin.application.edition', + type: 'keyword', + }, + 'gsuite.admin.application.name': { + category: 'gsuite', + description: "The application's name.", + name: 'gsuite.admin.application.name', + type: 'keyword', + }, + 'gsuite.admin.application.enabled': { + category: 'gsuite', + description: 'The enabled application.', + name: 'gsuite.admin.application.enabled', + type: 'keyword', + }, + 'gsuite.admin.application.licences_order_number': { + category: 'gsuite', + description: 'Order number used to redeem licenses.', + name: 'gsuite.admin.application.licences_order_number', + type: 'keyword', + }, + 'gsuite.admin.application.licences_purchased': { + category: 'gsuite', + description: 'Number of licences purchased.', + name: 'gsuite.admin.application.licences_purchased', + type: 'keyword', + }, + 'gsuite.admin.application.id': { + category: 'gsuite', + description: 'The application ID.', + name: 'gsuite.admin.application.id', + type: 'keyword', + }, + 'gsuite.admin.application.asp_id': { + category: 'gsuite', + description: 'The application specific password ID.', + name: 'gsuite.admin.application.asp_id', + type: 'keyword', + }, + 'gsuite.admin.application.package_id': { + category: 'gsuite', + description: 'The mobile application package ID.', + name: 'gsuite.admin.application.package_id', + type: 'keyword', + }, + 'gsuite.admin.group.email': { + category: 'gsuite', + description: "The group's primary email address.", + name: 'gsuite.admin.group.email', + type: 'keyword', + }, + 'gsuite.admin.new_value': { + category: 'gsuite', + description: 'The new value for the setting.', + name: 'gsuite.admin.new_value', + type: 'keyword', + }, + 'gsuite.admin.old_value': { + category: 'gsuite', + description: 'The old value for the setting.', + name: 'gsuite.admin.old_value', + type: 'keyword', + }, + 'gsuite.admin.org_unit.name': { + category: 'gsuite', + description: 'The organizational unit name.', + name: 'gsuite.admin.org_unit.name', + type: 'keyword', + }, + 'gsuite.admin.org_unit.full': { + category: 'gsuite', + description: 'The org unit full path including the root org unit name.', + name: 'gsuite.admin.org_unit.full', + type: 'keyword', + }, + 'gsuite.admin.setting.name': { + category: 'gsuite', + description: 'The setting name.', + name: 'gsuite.admin.setting.name', + type: 'keyword', + }, + 'gsuite.admin.user_defined_setting.name': { + category: 'gsuite', + description: 'The name of the user-defined setting.', + name: 'gsuite.admin.user_defined_setting.name', + type: 'keyword', + }, + 'gsuite.admin.setting.description': { + category: 'gsuite', + description: 'The setting name.', + name: 'gsuite.admin.setting.description', + type: 'keyword', + }, + 'gsuite.admin.group.priorities': { + category: 'gsuite', + description: 'Group priorities.', + name: 'gsuite.admin.group.priorities', + type: 'keyword', + }, + 'gsuite.admin.domain.alias': { + category: 'gsuite', + description: 'The domain alias.', + name: 'gsuite.admin.domain.alias', + type: 'keyword', + }, + 'gsuite.admin.domain.name': { + category: 'gsuite', + description: 'The primary domain name.', + name: 'gsuite.admin.domain.name', + type: 'keyword', + }, + 'gsuite.admin.domain.secondary_name': { + category: 'gsuite', + description: 'The secondary domain name.', + name: 'gsuite.admin.domain.secondary_name', + type: 'keyword', + }, + 'gsuite.admin.managed_configuration': { + category: 'gsuite', + description: 'The name of the managed configuration.', + name: 'gsuite.admin.managed_configuration', + type: 'keyword', + }, + 'gsuite.admin.non_featured_services_selection': { + category: 'gsuite', + description: + 'Non-featured services selection. For a list of possible values refer to https://developers.google.com/admin-sdk/reports/v1/appendix/activity/admin-application-settings#FLASHLIGHT_EDU_NON_FEATURED_SERVICES_SELECTED ', + name: 'gsuite.admin.non_featured_services_selection', + type: 'keyword', + }, + 'gsuite.admin.field': { + category: 'gsuite', + description: 'The name of the field.', + name: 'gsuite.admin.field', + type: 'keyword', + }, + 'gsuite.admin.resource.id': { + category: 'gsuite', + description: 'The name of the resource identifier.', + name: 'gsuite.admin.resource.id', + type: 'keyword', + }, + 'gsuite.admin.user.email': { + category: 'gsuite', + description: "The user's primary email address.", + name: 'gsuite.admin.user.email', + type: 'keyword', + }, + 'gsuite.admin.user.nickname': { + category: 'gsuite', + description: "The user's nickname.", + name: 'gsuite.admin.user.nickname', + type: 'keyword', + }, + 'gsuite.admin.user.birthdate': { + category: 'gsuite', + description: "The user's birth date.", + name: 'gsuite.admin.user.birthdate', + type: 'date', + }, + 'gsuite.admin.gateway.name': { + category: 'gsuite', + description: 'Gateway name. Present on some chat settings.', + name: 'gsuite.admin.gateway.name', + type: 'keyword', + }, + 'gsuite.admin.chrome_os.session_type': { + category: 'gsuite', + description: 'Chrome OS session type.', + name: 'gsuite.admin.chrome_os.session_type', + type: 'keyword', + }, + 'gsuite.admin.device.serial_number': { + category: 'gsuite', + description: 'Device serial number.', + name: 'gsuite.admin.device.serial_number', + type: 'keyword', + }, + 'gsuite.admin.device.id': { + category: 'gsuite', + name: 'gsuite.admin.device.id', + type: 'keyword', + }, + 'gsuite.admin.device.type': { + category: 'gsuite', + description: 'Device type.', + name: 'gsuite.admin.device.type', + type: 'keyword', + }, + 'gsuite.admin.print_server.name': { + category: 'gsuite', + description: 'The name of the print server.', + name: 'gsuite.admin.print_server.name', + type: 'keyword', + }, + 'gsuite.admin.printer.name': { + category: 'gsuite', + description: 'The name of the printer.', + name: 'gsuite.admin.printer.name', + type: 'keyword', + }, + 'gsuite.admin.device.command_details': { + category: 'gsuite', + description: 'Command details.', + name: 'gsuite.admin.device.command_details', + type: 'keyword', + }, + 'gsuite.admin.role.id': { + category: 'gsuite', + description: 'Unique identifier for this role privilege.', + name: 'gsuite.admin.role.id', + type: 'keyword', + }, + 'gsuite.admin.role.name': { + category: 'gsuite', + description: + 'The role name. For a list of possible values refer to https://developers.google.com/admin-sdk/reports/v1/appendix/activity/admin-delegated-admin-settings ', + name: 'gsuite.admin.role.name', + type: 'keyword', + }, + 'gsuite.admin.privilege.name': { + category: 'gsuite', + description: 'Privilege name.', + name: 'gsuite.admin.privilege.name', + type: 'keyword', + }, + 'gsuite.admin.service.name': { + category: 'gsuite', + description: 'The service name.', + name: 'gsuite.admin.service.name', + type: 'keyword', + }, + 'gsuite.admin.url.name': { + category: 'gsuite', + description: 'The website name.', + name: 'gsuite.admin.url.name', + type: 'keyword', + }, + 'gsuite.admin.product.name': { + category: 'gsuite', + description: 'The product name.', + name: 'gsuite.admin.product.name', + type: 'keyword', + }, + 'gsuite.admin.product.sku': { + category: 'gsuite', + description: 'The product SKU.', + name: 'gsuite.admin.product.sku', + type: 'keyword', + }, + 'gsuite.admin.bulk_upload.failed': { + category: 'gsuite', + description: 'Number of failed records in bulk upload operation.', + name: 'gsuite.admin.bulk_upload.failed', + type: 'long', + }, + 'gsuite.admin.bulk_upload.total': { + category: 'gsuite', + description: 'Number of total records in bulk upload operation.', + name: 'gsuite.admin.bulk_upload.total', + type: 'long', + }, + 'gsuite.admin.group.allowed_list': { + category: 'gsuite', + description: 'Names of allow-listed groups.', + name: 'gsuite.admin.group.allowed_list', + type: 'keyword', + }, + 'gsuite.admin.email.quarantine_name': { + category: 'gsuite', + description: 'The name of the quarantine.', + name: 'gsuite.admin.email.quarantine_name', + type: 'keyword', + }, + 'gsuite.admin.email.log_search_filter.message_id': { + category: 'gsuite', + description: "The log search filter's email message ID.", + name: 'gsuite.admin.email.log_search_filter.message_id', + type: 'keyword', + }, + 'gsuite.admin.email.log_search_filter.start_date': { + category: 'gsuite', + description: "The log search filter's start date.", + name: 'gsuite.admin.email.log_search_filter.start_date', + type: 'date', + }, + 'gsuite.admin.email.log_search_filter.end_date': { + category: 'gsuite', + description: "The log search filter's ending date.", + name: 'gsuite.admin.email.log_search_filter.end_date', + type: 'date', + }, + 'gsuite.admin.email.log_search_filter.recipient.value': { + category: 'gsuite', + description: "The log search filter's email recipient.", + name: 'gsuite.admin.email.log_search_filter.recipient.value', + type: 'keyword', + }, + 'gsuite.admin.email.log_search_filter.sender.value': { + category: 'gsuite', + description: "The log search filter's email sender.", + name: 'gsuite.admin.email.log_search_filter.sender.value', + type: 'keyword', + }, + 'gsuite.admin.email.log_search_filter.recipient.ip': { + category: 'gsuite', + description: "The log search filter's email recipient's IP address.", + name: 'gsuite.admin.email.log_search_filter.recipient.ip', + type: 'ip', + }, + 'gsuite.admin.email.log_search_filter.sender.ip': { + category: 'gsuite', + description: "The log search filter's email sender's IP address.", + name: 'gsuite.admin.email.log_search_filter.sender.ip', + type: 'ip', + }, + 'gsuite.admin.chrome_licenses.enabled': { + category: 'gsuite', + description: + 'Licences enabled. For a list of possible values refer to https://developers.google.com/admin-sdk/reports/v1/appendix/activity/admin-org-settings ', + name: 'gsuite.admin.chrome_licenses.enabled', + type: 'keyword', + }, + 'gsuite.admin.chrome_licenses.allowed': { + category: 'gsuite', + description: + 'Licences enabled. For a list of possible values refer to https://developers.google.com/admin-sdk/reports/v1/appendix/activity/admin-org-settings ', + name: 'gsuite.admin.chrome_licenses.allowed', + type: 'keyword', + }, + 'gsuite.admin.oauth2.service.name': { + category: 'gsuite', + description: + 'OAuth2 service name. For a list of possible values refer to https://developers.google.com/admin-sdk/reports/v1/appendix/activity/admin-security-settings ', + name: 'gsuite.admin.oauth2.service.name', + type: 'keyword', + }, + 'gsuite.admin.oauth2.application.id': { + category: 'gsuite', + description: 'OAuth2 application ID.', + name: 'gsuite.admin.oauth2.application.id', + type: 'keyword', + }, + 'gsuite.admin.oauth2.application.name': { + category: 'gsuite', + description: 'OAuth2 application name.', + name: 'gsuite.admin.oauth2.application.name', + type: 'keyword', + }, + 'gsuite.admin.oauth2.application.type': { + category: 'gsuite', + description: + 'OAuth2 application type. For a list of possible values refer to https://developers.google.com/admin-sdk/reports/v1/appendix/activity/admin-security-settings ', + name: 'gsuite.admin.oauth2.application.type', + type: 'keyword', + }, + 'gsuite.admin.verification_method': { + category: 'gsuite', + description: + 'Related verification method. For a list of possible values refer to https://developers.google.com/admin-sdk/reports/v1/appendix/activity/admin-security-settings and https://developers.google.com/admin-sdk/reports/v1/appendix/activity/admin-domain-settings ', + name: 'gsuite.admin.verification_method', + type: 'keyword', + }, + 'gsuite.admin.alert.name': { + category: 'gsuite', + description: 'The alert name.', + name: 'gsuite.admin.alert.name', + type: 'keyword', + }, + 'gsuite.admin.rule.name': { + category: 'gsuite', + description: 'The rule name.', + name: 'gsuite.admin.rule.name', + type: 'keyword', + }, + 'gsuite.admin.api.client.name': { + category: 'gsuite', + description: 'The API client name.', + name: 'gsuite.admin.api.client.name', + type: 'keyword', + }, + 'gsuite.admin.api.scopes': { + category: 'gsuite', + description: 'The API scopes.', + name: 'gsuite.admin.api.scopes', + type: 'keyword', + }, + 'gsuite.admin.mdm.token': { + category: 'gsuite', + description: 'The MDM vendor enrollment token.', + name: 'gsuite.admin.mdm.token', + type: 'keyword', + }, + 'gsuite.admin.mdm.vendor': { + category: 'gsuite', + description: "The MDM vendor's name.", + name: 'gsuite.admin.mdm.vendor', + type: 'keyword', + }, + 'gsuite.admin.info_type': { + category: 'gsuite', + description: + 'This will be used to state what kind of information was changed. For a list of possible values refer to https://developers.google.com/admin-sdk/reports/v1/appendix/activity/admin-domain-settings ', + name: 'gsuite.admin.info_type', + type: 'keyword', + }, + 'gsuite.admin.email_monitor.dest_email': { + category: 'gsuite', + description: 'The destination address of the email monitor.', + name: 'gsuite.admin.email_monitor.dest_email', + type: 'keyword', + }, + 'gsuite.admin.email_monitor.level.chat': { + category: 'gsuite', + description: 'The chat email monitor level.', + name: 'gsuite.admin.email_monitor.level.chat', + type: 'keyword', + }, + 'gsuite.admin.email_monitor.level.draft': { + category: 'gsuite', + description: 'The draft email monitor level.', + name: 'gsuite.admin.email_monitor.level.draft', + type: 'keyword', + }, + 'gsuite.admin.email_monitor.level.incoming': { + category: 'gsuite', + description: 'The incoming email monitor level.', + name: 'gsuite.admin.email_monitor.level.incoming', + type: 'keyword', + }, + 'gsuite.admin.email_monitor.level.outgoing': { + category: 'gsuite', + description: 'The outgoing email monitor level.', + name: 'gsuite.admin.email_monitor.level.outgoing', + type: 'keyword', + }, + 'gsuite.admin.email_dump.include_deleted': { + category: 'gsuite', + description: 'Indicates if deleted emails are included in the export.', + name: 'gsuite.admin.email_dump.include_deleted', + type: 'boolean', + }, + 'gsuite.admin.email_dump.package_content': { + category: 'gsuite', + description: 'The contents of the mailbox package.', + name: 'gsuite.admin.email_dump.package_content', + type: 'keyword', + }, + 'gsuite.admin.email_dump.query': { + category: 'gsuite', + description: 'The search query used for the dump.', + name: 'gsuite.admin.email_dump.query', + type: 'keyword', + }, + 'gsuite.admin.request.id': { + category: 'gsuite', + description: 'The request ID.', + name: 'gsuite.admin.request.id', + type: 'keyword', + }, + 'gsuite.admin.mobile.action.id': { + category: 'gsuite', + description: "The mobile device action's ID.", + name: 'gsuite.admin.mobile.action.id', + type: 'keyword', + }, + 'gsuite.admin.mobile.action.type': { + category: 'gsuite', + description: + "The mobile device action's type. For a list of possible values refer to https://developers.google.com/admin-sdk/reports/v1/appendix/activity/admin-mobile-settings ", + name: 'gsuite.admin.mobile.action.type', + type: 'keyword', + }, + 'gsuite.admin.mobile.certificate.name': { + category: 'gsuite', + description: 'The mobile certificate common name.', + name: 'gsuite.admin.mobile.certificate.name', + type: 'keyword', + }, + 'gsuite.admin.mobile.company_owned_devices': { + category: 'gsuite', + description: 'The number of devices a company owns.', + name: 'gsuite.admin.mobile.company_owned_devices', + type: 'long', + }, + 'gsuite.admin.distribution.entity.name': { + category: 'gsuite', + description: + 'The distribution entity value, which can be a group name or an org-unit name. For a list of possible values refer to https://developers.google.com/admin-sdk/reports/v1/appendix/activity/admin-mobile-settings ', + name: 'gsuite.admin.distribution.entity.name', + type: 'keyword', + }, + 'gsuite.admin.distribution.entity.type': { + category: 'gsuite', + description: + 'The distribution entity type, which can be a group or an org-unit. For a list of possible values refer to https://developers.google.com/admin-sdk/reports/v1/appendix/activity/admin-mobile-settings ', + name: 'gsuite.admin.distribution.entity.type', + type: 'keyword', + }, + 'gsuite.drive.billable': { + category: 'gsuite', + description: 'Whether this activity is billable.', + name: 'gsuite.drive.billable', + type: 'boolean', + }, + 'gsuite.drive.source_folder_id': { + category: 'gsuite', + name: 'gsuite.drive.source_folder_id', + type: 'keyword', + }, + 'gsuite.drive.source_folder_title': { + category: 'gsuite', + name: 'gsuite.drive.source_folder_title', + type: 'keyword', + }, + 'gsuite.drive.destination_folder_id': { + category: 'gsuite', + name: 'gsuite.drive.destination_folder_id', + type: 'keyword', + }, + 'gsuite.drive.destination_folder_title': { + category: 'gsuite', + name: 'gsuite.drive.destination_folder_title', + type: 'keyword', + }, + 'gsuite.drive.file.id': { + category: 'gsuite', + name: 'gsuite.drive.file.id', + type: 'keyword', + }, + 'gsuite.drive.file.type': { + category: 'gsuite', + description: + 'Document Drive type. For a list of possible values refer to https://developers.google.com/admin-sdk/reports/v1/appendix/activity/drive ', + name: 'gsuite.drive.file.type', + type: 'keyword', + }, + 'gsuite.drive.originating_app_id': { + category: 'gsuite', + description: 'The Google Cloud Project ID of the application that performed the action. ', + name: 'gsuite.drive.originating_app_id', + type: 'keyword', + }, + 'gsuite.drive.file.owner.email': { + category: 'gsuite', + name: 'gsuite.drive.file.owner.email', + type: 'keyword', + }, + 'gsuite.drive.file.owner.is_shared_drive': { + category: 'gsuite', + description: 'Boolean flag denoting whether owner is a shared drive. ', + name: 'gsuite.drive.file.owner.is_shared_drive', + type: 'boolean', + }, + 'gsuite.drive.primary_event': { + category: 'gsuite', + description: + 'Whether this is a primary event. A single user action in Drive may generate several events. ', + name: 'gsuite.drive.primary_event', + type: 'boolean', + }, + 'gsuite.drive.shared_drive_id': { + category: 'gsuite', + description: + 'The unique identifier of the Team Drive. Only populated for for events relating to a Team Drive or item contained inside a Team Drive. ', + name: 'gsuite.drive.shared_drive_id', + type: 'keyword', + }, + 'gsuite.drive.visibility': { + category: 'gsuite', + description: + 'Visibility of target file. For a list of possible values refer to https://developers.google.com/admin-sdk/reports/v1/appendix/activity/drive ', + name: 'gsuite.drive.visibility', + type: 'keyword', + }, + 'gsuite.drive.new_value': { + category: 'gsuite', + description: + 'When a setting or property of the file changes, the new value for it will appear here. ', + name: 'gsuite.drive.new_value', + type: 'keyword', + }, + 'gsuite.drive.old_value': { + category: 'gsuite', + description: + 'When a setting or property of the file changes, the old value for it will appear here. ', + name: 'gsuite.drive.old_value', + type: 'keyword', + }, + 'gsuite.drive.sheets_import_range_recipient_doc': { + category: 'gsuite', + description: 'Doc ID of the recipient of a sheets import range.', + name: 'gsuite.drive.sheets_import_range_recipient_doc', + type: 'keyword', + }, + 'gsuite.drive.old_visibility': { + category: 'gsuite', + description: 'When visibility changes, this holds the old value. ', + name: 'gsuite.drive.old_visibility', + type: 'keyword', + }, + 'gsuite.drive.visibility_change': { + category: 'gsuite', + description: 'When visibility changes, this holds the new overall visibility of the file. ', + name: 'gsuite.drive.visibility_change', + type: 'keyword', + }, + 'gsuite.drive.target_domain': { + category: 'gsuite', + description: + 'The domain for which the acccess scope was changed. This can also be the alias all to indicate the access scope was changed for all domains that have visibility for this document. ', + name: 'gsuite.drive.target_domain', + type: 'keyword', + }, + 'gsuite.drive.added_role': { + category: 'gsuite', + description: + 'Added membership role of a user/group in a Team Drive. For a list of possible values refer to https://developers.google.com/admin-sdk/reports/v1/appendix/activity/drive ', + name: 'gsuite.drive.added_role', + type: 'keyword', + }, + 'gsuite.drive.membership_change_type': { + category: 'gsuite', + description: + 'Type of change in Team Drive membership of a user/group. For a list of possible values refer to https://developers.google.com/admin-sdk/reports/v1/appendix/activity/drive ', + name: 'gsuite.drive.membership_change_type', + type: 'keyword', + }, + 'gsuite.drive.shared_drive_settings_change_type': { + category: 'gsuite', + description: + 'Type of change in Team Drive settings. For a list of possible values refer to https://developers.google.com/admin-sdk/reports/v1/appendix/activity/drive ', + name: 'gsuite.drive.shared_drive_settings_change_type', + type: 'keyword', + }, + 'gsuite.drive.removed_role': { + category: 'gsuite', + description: + 'Removed membership role of a user/group in a Team Drive. For a list of possible values refer to https://developers.google.com/admin-sdk/reports/v1/appendix/activity/drive ', + name: 'gsuite.drive.removed_role', + type: 'keyword', + }, + 'gsuite.drive.target': { + category: 'gsuite', + description: 'Target user or group.', + name: 'gsuite.drive.target', + type: 'keyword', + }, + 'gsuite.groups.acl_permission': { + category: 'gsuite', + description: + 'Group permission setting updated. For a list of possible values refer to https://developers.google.com/admin-sdk/reports/v1/appendix/activity/groups ', + name: 'gsuite.groups.acl_permission', + type: 'keyword', + }, + 'gsuite.groups.email': { + category: 'gsuite', + description: 'Group email. ', + name: 'gsuite.groups.email', + type: 'keyword', + }, + 'gsuite.groups.member.email': { + category: 'gsuite', + description: 'Member email. ', + name: 'gsuite.groups.member.email', + type: 'keyword', + }, + 'gsuite.groups.member.role': { + category: 'gsuite', + description: + 'Member role. For a list of possible values refer to https://developers.google.com/admin-sdk/reports/v1/appendix/activity/groups ', + name: 'gsuite.groups.member.role', + type: 'keyword', + }, + 'gsuite.groups.setting': { + category: 'gsuite', + description: + 'Group setting updated. For a list of possible values refer to https://developers.google.com/admin-sdk/reports/v1/appendix/activity/groups ', + name: 'gsuite.groups.setting', + type: 'keyword', + }, + 'gsuite.groups.new_value': { + category: 'gsuite', + description: + 'New value(s) of the group setting. For a list of possible values refer to https://developers.google.com/admin-sdk/reports/v1/appendix/activity/groups ', + name: 'gsuite.groups.new_value', + type: 'keyword', + }, + 'gsuite.groups.old_value': { + category: 'gsuite', + description: + 'Old value(s) of the group setting. For a list of possible values refer to https://developers.google.com/admin-sdk/reports/v1/appendix/activity/groups', + name: 'gsuite.groups.old_value', + type: 'keyword', + }, + 'gsuite.groups.value': { + category: 'gsuite', + description: + 'Value of the group setting. For a list of possible values refer to https://developers.google.com/admin-sdk/reports/v1/appendix/activity/groups ', + name: 'gsuite.groups.value', + type: 'keyword', + }, + 'gsuite.groups.message.id': { + category: 'gsuite', + description: 'SMTP message Id of an email message. Present for moderation events. ', + name: 'gsuite.groups.message.id', + type: 'keyword', + }, + 'gsuite.groups.message.moderation_action': { + category: 'gsuite', + description: 'Message moderation action. Possible values are `approved` and `rejected`. ', + name: 'gsuite.groups.message.moderation_action', + type: 'keyword', + }, + 'gsuite.groups.status': { + category: 'gsuite', + description: + 'A status describing the output of an operation. Possible values are `failed` and `succeeded`. ', + name: 'gsuite.groups.status', + type: 'keyword', + }, + 'gsuite.login.affected_email_address': { + category: 'gsuite', + name: 'gsuite.login.affected_email_address', + type: 'keyword', + }, + 'gsuite.login.challenge_method': { + category: 'gsuite', + description: + 'Login challenge method. For a list of possible values refer to https://developers.google.com/admin-sdk/reports/v1/appendix/activity/login. ', + name: 'gsuite.login.challenge_method', + type: 'keyword', + }, + 'gsuite.login.failure_type': { + category: 'gsuite', + description: + 'Login failure type. For a list of possible values refer to https://developers.google.com/admin-sdk/reports/v1/appendix/activity/login. ', + name: 'gsuite.login.failure_type', + type: 'keyword', + }, + 'gsuite.login.type': { + category: 'gsuite', + description: + 'Login credentials type. For a list of possible values refer to https://developers.google.com/admin-sdk/reports/v1/appendix/activity/login. ', + name: 'gsuite.login.type', + type: 'keyword', + }, + 'gsuite.login.is_second_factor': { + category: 'gsuite', + name: 'gsuite.login.is_second_factor', + type: 'boolean', + }, + 'gsuite.login.is_suspicious': { + category: 'gsuite', + name: 'gsuite.login.is_suspicious', + type: 'boolean', + }, + 'gsuite.saml.application_name': { + category: 'gsuite', + description: 'Saml SP application name. ', + name: 'gsuite.saml.application_name', + type: 'keyword', + }, + 'gsuite.saml.failure_type': { + category: 'gsuite', + description: + 'Login failure type. For a list of possible values refer to https://developers.google.com/admin-sdk/reports/v1/appendix/activity/saml. ', + name: 'gsuite.saml.failure_type', + type: 'keyword', + }, + 'gsuite.saml.initiated_by': { + category: 'gsuite', + description: 'Requester of SAML authentication. ', + name: 'gsuite.saml.initiated_by', + type: 'keyword', + }, + 'gsuite.saml.orgunit_path': { + category: 'gsuite', + description: 'User orgunit. ', + name: 'gsuite.saml.orgunit_path', + type: 'keyword', + }, + 'gsuite.saml.status_code': { + category: 'gsuite', + description: 'SAML status code. ', + name: 'gsuite.saml.status_code', + type: 'long', + }, + 'gsuite.saml.second_level_status_code': { + category: 'gsuite', + description: 'SAML second level status code. ', + name: 'gsuite.saml.second_level_status_code', + type: 'long', + }, + 'ibmmq.errorlog.installation': { + category: 'ibmmq', + description: + 'This is the installation name which can be given at installation time. Each installation of IBM MQ on UNIX, Linux, and Windows, has a unique identifier known as an installation name. The installation name is used to associate things such as queue managers and configuration files with an installation. ', + name: 'ibmmq.errorlog.installation', + type: 'keyword', + }, + 'ibmmq.errorlog.qmgr': { + category: 'ibmmq', + description: + 'Name of the queue manager. Queue managers provide queuing services to applications, and manages the queues that belong to them. ', + name: 'ibmmq.errorlog.qmgr', + type: 'keyword', + }, + 'ibmmq.errorlog.arithinsert': { + category: 'ibmmq', + description: 'Changing content based on error.id', + name: 'ibmmq.errorlog.arithinsert', + type: 'keyword', + }, + 'ibmmq.errorlog.commentinsert': { + category: 'ibmmq', + description: 'Changing content based on error.id', + name: 'ibmmq.errorlog.commentinsert', + type: 'keyword', + }, + 'ibmmq.errorlog.errordescription': { + category: 'ibmmq', + description: 'Please add description', + example: 'Please add example', + name: 'ibmmq.errorlog.errordescription', + type: 'text', + }, + 'ibmmq.errorlog.explanation': { + category: 'ibmmq', + description: 'Explaines the error in more detail', + name: 'ibmmq.errorlog.explanation', + type: 'keyword', + }, + 'ibmmq.errorlog.action': { + category: 'ibmmq', + description: 'Defines what to do when the error occurs', + name: 'ibmmq.errorlog.action', + type: 'keyword', + }, + 'ibmmq.errorlog.code': { + category: 'ibmmq', + description: 'Error code.', + name: 'ibmmq.errorlog.code', + type: 'keyword', + }, + 'iptables.ether_type': { + category: 'iptables', + description: 'Value of the ethernet type field identifying the network layer protocol. ', + name: 'iptables.ether_type', + type: 'long', + }, + 'iptables.flow_label': { + category: 'iptables', + description: 'IPv6 flow label. ', + name: 'iptables.flow_label', + type: 'integer', + }, + 'iptables.fragment_flags': { + category: 'iptables', + description: 'IP fragment flags. A combination of CE, DF and MF. ', + name: 'iptables.fragment_flags', + type: 'keyword', + }, + 'iptables.fragment_offset': { + category: 'iptables', + description: 'Offset of the current IP fragment. ', + name: 'iptables.fragment_offset', + type: 'long', + }, + 'iptables.icmp.code': { + category: 'iptables', + description: 'ICMP code. ', + name: 'iptables.icmp.code', + type: 'long', + }, + 'iptables.icmp.id': { + category: 'iptables', + description: 'ICMP ID. ', + name: 'iptables.icmp.id', + type: 'long', + }, + 'iptables.icmp.parameter': { + category: 'iptables', + description: 'ICMP parameter. ', + name: 'iptables.icmp.parameter', + type: 'long', + }, + 'iptables.icmp.redirect': { + category: 'iptables', + description: 'ICMP redirect address. ', + name: 'iptables.icmp.redirect', + type: 'ip', + }, + 'iptables.icmp.seq': { + category: 'iptables', + description: 'ICMP sequence number. ', + name: 'iptables.icmp.seq', + type: 'long', + }, + 'iptables.icmp.type': { + category: 'iptables', + description: 'ICMP type. ', + name: 'iptables.icmp.type', + type: 'long', + }, + 'iptables.id': { + category: 'iptables', + description: 'Packet identifier. ', + name: 'iptables.id', + type: 'long', + }, + 'iptables.incomplete_bytes': { + category: 'iptables', + description: 'Number of incomplete bytes. ', + name: 'iptables.incomplete_bytes', + type: 'long', + }, + 'iptables.input_device': { + category: 'iptables', + description: 'Device that received the packet. ', + name: 'iptables.input_device', + type: 'keyword', + }, + 'iptables.precedence_bits': { + category: 'iptables', + description: 'IP precedence bits. ', + name: 'iptables.precedence_bits', + type: 'short', + }, + 'iptables.tos': { + category: 'iptables', + description: 'IP Type of Service field. ', + name: 'iptables.tos', + type: 'long', + }, + 'iptables.length': { + category: 'iptables', + description: 'Packet length. ', + name: 'iptables.length', + type: 'long', + }, + 'iptables.output_device': { + category: 'iptables', + description: 'Device that output the packet. ', + name: 'iptables.output_device', + type: 'keyword', + }, + 'iptables.tcp.flags': { + category: 'iptables', + description: 'TCP flags. ', + name: 'iptables.tcp.flags', + type: 'keyword', + }, + 'iptables.tcp.reserved_bits': { + category: 'iptables', + description: 'TCP reserved bits. ', + name: 'iptables.tcp.reserved_bits', + type: 'short', + }, + 'iptables.tcp.seq': { + category: 'iptables', + description: 'TCP sequence number. ', + name: 'iptables.tcp.seq', + type: 'long', + }, + 'iptables.tcp.ack': { + category: 'iptables', + description: 'TCP Acknowledgment number. ', + name: 'iptables.tcp.ack', + type: 'long', + }, + 'iptables.tcp.window': { + category: 'iptables', + description: 'Advertised TCP window size. ', + name: 'iptables.tcp.window', + type: 'long', + }, + 'iptables.ttl': { + category: 'iptables', + description: 'Time To Live field. ', + name: 'iptables.ttl', + type: 'integer', + }, + 'iptables.udp.length': { + category: 'iptables', + description: 'Length of the UDP header and payload. ', + name: 'iptables.udp.length', + type: 'long', + }, + 'iptables.ubiquiti.input_zone': { + category: 'iptables', + description: 'Input zone. ', + name: 'iptables.ubiquiti.input_zone', + type: 'keyword', + }, + 'iptables.ubiquiti.output_zone': { + category: 'iptables', + description: 'Output zone. ', + name: 'iptables.ubiquiti.output_zone', + type: 'keyword', + }, + 'iptables.ubiquiti.rule_number': { + category: 'iptables', + description: 'The rule number within the rule set.', + name: 'iptables.ubiquiti.rule_number', + type: 'keyword', + }, + 'iptables.ubiquiti.rule_set': { + category: 'iptables', + description: 'The rule set name.', + name: 'iptables.ubiquiti.rule_set', + type: 'keyword', + }, + 'microsoft.defender_atp.lastUpdateTime': { + category: 'microsoft', + description: 'The date and time (in UTC) the alert was last updated. ', + name: 'microsoft.defender_atp.lastUpdateTime', + type: 'date', + }, + 'microsoft.defender_atp.resolvedTime': { + category: 'microsoft', + description: "The date and time in which the status of the alert was changed to 'Resolved'. ", + name: 'microsoft.defender_atp.resolvedTime', + type: 'date', + }, + 'microsoft.defender_atp.incidentId': { + category: 'microsoft', + description: 'The Incident ID of the Alert. ', + name: 'microsoft.defender_atp.incidentId', + type: 'keyword', + }, + 'microsoft.defender_atp.investigationId': { + category: 'microsoft', + description: 'The Investigation ID related to the Alert. ', + name: 'microsoft.defender_atp.investigationId', + type: 'keyword', + }, + 'microsoft.defender_atp.investigationState': { + category: 'microsoft', + description: 'The current state of the Investigation. ', + name: 'microsoft.defender_atp.investigationState', + type: 'keyword', + }, + 'microsoft.defender_atp.assignedTo': { + category: 'microsoft', + description: 'Owner of the alert. ', + name: 'microsoft.defender_atp.assignedTo', + type: 'keyword', + }, + 'microsoft.defender_atp.status': { + category: 'microsoft', + description: + "Specifies the current status of the alert. Possible values are: 'Unknown', 'New', 'InProgress' and 'Resolved'. ", + name: 'microsoft.defender_atp.status', + type: 'keyword', + }, + 'microsoft.defender_atp.classification': { + category: 'microsoft', + description: + "Specification of the alert. Possible values are: 'Unknown', 'FalsePositive', 'TruePositive'. ", + name: 'microsoft.defender_atp.classification', + type: 'keyword', + }, + 'microsoft.defender_atp.determination': { + category: 'microsoft', + description: + "Specifies the determination of the alert. Possible values are: 'NotAvailable', 'Apt', 'Malware', 'SecurityPersonnel', 'SecurityTesting', 'UnwantedSoftware', 'Other'. ", + name: 'microsoft.defender_atp.determination', + type: 'keyword', + }, + 'microsoft.defender_atp.threatFamilyName': { + category: 'microsoft', + description: 'Threat family. ', + name: 'microsoft.defender_atp.threatFamilyName', + type: 'keyword', + }, + 'microsoft.defender_atp.rbacGroupName': { + category: 'microsoft', + description: 'User group related to the alert ', + name: 'microsoft.defender_atp.rbacGroupName', + type: 'keyword', + }, + 'microsoft.defender_atp.evidence.domainName': { + category: 'microsoft', + description: 'Domain name related to the alert ', + name: 'microsoft.defender_atp.evidence.domainName', + type: 'keyword', + }, + 'microsoft.defender_atp.evidence.ipAddress': { + category: 'microsoft', + description: 'IP address involved in the alert ', + name: 'microsoft.defender_atp.evidence.ipAddress', + type: 'ip', + }, + 'microsoft.defender_atp.evidence.aadUserId': { + category: 'microsoft', + description: 'ID of the user involved in the alert ', + name: 'microsoft.defender_atp.evidence.aadUserId', + type: 'keyword', + }, + 'microsoft.defender_atp.evidence.accountName': { + category: 'microsoft', + description: 'Username of the user involved in the alert ', + name: 'microsoft.defender_atp.evidence.accountName', + type: 'keyword', + }, + 'microsoft.defender_atp.evidence.entityType': { + category: 'microsoft', + description: 'The type of evidence ', + name: 'microsoft.defender_atp.evidence.entityType', + type: 'keyword', + }, + 'microsoft.defender_atp.evidence.userPrincipalName': { + category: 'microsoft', + description: 'Principal name of the user involved in the alert ', + name: 'microsoft.defender_atp.evidence.userPrincipalName', + type: 'keyword', + }, + 'misp.attack_pattern.id': { + category: 'misp', + description: 'Identifier of the threat indicator. ', + name: 'misp.attack_pattern.id', + type: 'keyword', + }, + 'misp.attack_pattern.name': { + category: 'misp', + description: 'Name of the attack pattern. ', + name: 'misp.attack_pattern.name', + type: 'keyword', + }, + 'misp.attack_pattern.description': { + category: 'misp', + description: 'Description of the attack pattern. ', + name: 'misp.attack_pattern.description', + type: 'text', + }, + 'misp.attack_pattern.kill_chain_phases': { + category: 'misp', + description: 'The kill chain phase(s) to which this attack pattern corresponds. ', + name: 'misp.attack_pattern.kill_chain_phases', + type: 'keyword', + }, + 'misp.campaign.id': { + category: 'misp', + description: 'Identifier of the campaign. ', + name: 'misp.campaign.id', + type: 'keyword', + }, + 'misp.campaign.name': { + category: 'misp', + description: 'Name of the campaign. ', + name: 'misp.campaign.name', + type: 'keyword', + }, + 'misp.campaign.description': { + category: 'misp', + description: 'Description of the campaign. ', + name: 'misp.campaign.description', + type: 'text', + }, + 'misp.campaign.aliases': { + category: 'misp', + description: 'Alternative names used to identify this campaign. ', + name: 'misp.campaign.aliases', + type: 'text', + }, + 'misp.campaign.first_seen': { + category: 'misp', + description: 'The time that this Campaign was first seen, in RFC3339 format. ', + name: 'misp.campaign.first_seen', + type: 'date', + }, + 'misp.campaign.last_seen': { + category: 'misp', + description: 'The time that this Campaign was last seen, in RFC3339 format. ', + name: 'misp.campaign.last_seen', + type: 'date', + }, + 'misp.campaign.objective': { + category: 'misp', + description: + "This field defines the Campaign's primary goal, objective, desired outcome, or intended effect. ", + name: 'misp.campaign.objective', + type: 'keyword', + }, + 'misp.course_of_action.id': { + category: 'misp', + description: 'Identifier of the Course of Action. ', + name: 'misp.course_of_action.id', + type: 'keyword', + }, + 'misp.course_of_action.name': { + category: 'misp', + description: 'The name used to identify the Course of Action. ', + name: 'misp.course_of_action.name', + type: 'keyword', + }, + 'misp.course_of_action.description': { + category: 'misp', + description: 'Description of the Course of Action. ', + name: 'misp.course_of_action.description', + type: 'text', + }, + 'misp.identity.id': { + category: 'misp', + description: 'Identifier of the Identity. ', + name: 'misp.identity.id', + type: 'keyword', + }, + 'misp.identity.name': { + category: 'misp', + description: 'The name used to identify the Identity. ', + name: 'misp.identity.name', + type: 'keyword', + }, + 'misp.identity.description': { + category: 'misp', + description: 'Description of the Identity. ', + name: 'misp.identity.description', + type: 'text', + }, + 'misp.identity.identity_class': { + category: 'misp', + description: + 'The type of entity that this Identity describes, e.g., an individual or organization. Open Vocab - identity-class-ov ', + name: 'misp.identity.identity_class', + type: 'keyword', + }, + 'misp.identity.labels': { + category: 'misp', + description: 'The list of roles that this Identity performs. ', + example: 'CEO\n', + name: 'misp.identity.labels', + type: 'keyword', + }, + 'misp.identity.sectors': { + category: 'misp', + description: + 'The list of sectors that this Identity belongs to. Open Vocab - industry-sector-ov ', + name: 'misp.identity.sectors', + type: 'keyword', + }, + 'misp.identity.contact_information': { + category: 'misp', + description: 'The contact information (e-mail, phone number, etc.) for this Identity. ', + name: 'misp.identity.contact_information', + type: 'text', + }, + 'misp.intrusion_set.id': { + category: 'misp', + description: 'Identifier of the Intrusion Set. ', + name: 'misp.intrusion_set.id', + type: 'keyword', + }, + 'misp.intrusion_set.name': { + category: 'misp', + description: 'The name used to identify the Intrusion Set. ', + name: 'misp.intrusion_set.name', + type: 'keyword', + }, + 'misp.intrusion_set.description': { + category: 'misp', + description: 'Description of the Intrusion Set. ', + name: 'misp.intrusion_set.description', + type: 'text', + }, + 'misp.intrusion_set.aliases': { + category: 'misp', + description: 'Alternative names used to identify the Intrusion Set. ', + name: 'misp.intrusion_set.aliases', + type: 'text', + }, + 'misp.intrusion_set.first_seen': { + category: 'misp', + description: 'The time that this Intrusion Set was first seen, in RFC3339 format. ', + name: 'misp.intrusion_set.first_seen', + type: 'date', + }, + 'misp.intrusion_set.last_seen': { + category: 'misp', + description: 'The time that this Intrusion Set was last seen, in RFC3339 format. ', + name: 'misp.intrusion_set.last_seen', + type: 'date', + }, + 'misp.intrusion_set.goals': { + category: 'misp', + description: 'The high level goals of this Intrusion Set, namely, what are they trying to do. ', + name: 'misp.intrusion_set.goals', + type: 'text', + }, + 'misp.intrusion_set.resource_level': { + category: 'misp', + description: + 'This defines the organizational level at which this Intrusion Set typically works. Open Vocab - attack-resource-level-ov ', + name: 'misp.intrusion_set.resource_level', + type: 'text', + }, + 'misp.intrusion_set.primary_motivation': { + category: 'misp', + description: + 'The primary reason, motivation, or purpose behind this Intrusion Set. Open Vocab - attack-motivation-ov ', + name: 'misp.intrusion_set.primary_motivation', + type: 'text', + }, + 'misp.intrusion_set.secondary_motivations': { + category: 'misp', + description: + 'The secondary reasons, motivations, or purposes behind this Intrusion Set. Open Vocab - attack-motivation-ov ', + name: 'misp.intrusion_set.secondary_motivations', + type: 'text', + }, + 'misp.malware.id': { + category: 'misp', + description: 'Identifier of the Malware. ', + name: 'misp.malware.id', + type: 'keyword', + }, + 'misp.malware.name': { + category: 'misp', + description: 'The name used to identify the Malware. ', + name: 'misp.malware.name', + type: 'keyword', + }, + 'misp.malware.description': { + category: 'misp', + description: 'Description of the Malware. ', + name: 'misp.malware.description', + type: 'text', + }, + 'misp.malware.labels': { + category: 'misp', + description: + 'The type of malware being described. Open Vocab - malware-label-ov. adware,backdoor,bot,ddos,dropper,exploit-kit,keylogger,ransomware, remote-access-trojan,resource-exploitation,rogue-security-software,rootkit, screen-capture,spyware,trojan,virus,worm ', + name: 'misp.malware.labels', + type: 'keyword', + }, + 'misp.malware.kill_chain_phases': { + category: 'misp', + description: 'The list of kill chain phases for which this Malware instance can be used. ', + name: 'misp.malware.kill_chain_phases', + type: 'keyword', + format: 'string', + }, + 'misp.note.id': { + category: 'misp', + description: 'Identifier of the Note. ', + name: 'misp.note.id', + type: 'keyword', + }, + 'misp.note.summary': { + category: 'misp', + description: 'A brief description used as a summary of the Note. ', + name: 'misp.note.summary', + type: 'keyword', + }, + 'misp.note.description': { + category: 'misp', + description: 'The content of the Note. ', + name: 'misp.note.description', + type: 'text', + }, + 'misp.note.authors': { + category: 'misp', + description: 'The name of the author(s) of this Note. ', + name: 'misp.note.authors', + type: 'keyword', + }, + 'misp.note.object_refs': { + category: 'misp', + description: 'The STIX Objects (SDOs and SROs) that the note is being applied to. ', + name: 'misp.note.object_refs', + type: 'keyword', + }, + 'misp.threat_indicator.labels': { + category: 'misp', + description: 'list of type open-vocab that specifies the type of indicator. ', + example: 'Domain Watchlist\n', + name: 'misp.threat_indicator.labels', + type: 'keyword', + }, + 'misp.threat_indicator.id': { + category: 'misp', + description: 'Identifier of the threat indicator. ', + name: 'misp.threat_indicator.id', + type: 'keyword', + }, + 'misp.threat_indicator.version': { + category: 'misp', + description: 'Version of the threat indicator. ', + name: 'misp.threat_indicator.version', + type: 'keyword', + }, + 'misp.threat_indicator.type': { + category: 'misp', + description: 'Type of the threat indicator. ', + name: 'misp.threat_indicator.type', + type: 'keyword', + }, + 'misp.threat_indicator.description': { + category: 'misp', + description: 'Description of the threat indicator. ', + name: 'misp.threat_indicator.description', + type: 'text', + }, + 'misp.threat_indicator.feed': { + category: 'misp', + description: 'Name of the threat feed. ', + name: 'misp.threat_indicator.feed', + type: 'text', + }, + 'misp.threat_indicator.valid_from': { + category: 'misp', + description: + 'The time from which this Indicator should be considered valuable intelligence, in RFC3339 format. ', + name: 'misp.threat_indicator.valid_from', + type: 'date', + }, + 'misp.threat_indicator.valid_until': { + category: 'misp', + description: + 'The time at which this Indicator should no longer be considered valuable intelligence. If the valid_until property is omitted, then there is no constraint on the latest time for which the indicator should be used, in RFC3339 format. ', + name: 'misp.threat_indicator.valid_until', + type: 'date', + }, + 'misp.threat_indicator.severity': { + category: 'misp', + description: 'Threat severity to which this indicator corresponds. ', + example: 'high', + name: 'misp.threat_indicator.severity', + type: 'keyword', + format: 'string', + }, + 'misp.threat_indicator.confidence': { + category: 'misp', + description: 'Confidence level to which this indicator corresponds. ', + example: 'high', + name: 'misp.threat_indicator.confidence', + type: 'keyword', + }, + 'misp.threat_indicator.kill_chain_phases': { + category: 'misp', + description: 'The kill chain phase(s) to which this indicator corresponds. ', + name: 'misp.threat_indicator.kill_chain_phases', + type: 'keyword', + format: 'string', + }, + 'misp.threat_indicator.mitre_tactic': { + category: 'misp', + description: 'MITRE tactics to which this indicator corresponds. ', + example: 'Initial Access', + name: 'misp.threat_indicator.mitre_tactic', + type: 'keyword', + format: 'string', + }, + 'misp.threat_indicator.mitre_technique': { + category: 'misp', + description: 'MITRE techniques to which this indicator corresponds. ', + example: 'Drive-by Compromise', + name: 'misp.threat_indicator.mitre_technique', + type: 'keyword', + format: 'string', + }, + 'misp.threat_indicator.attack_pattern': { + category: 'misp', + description: + 'The attack_pattern for this indicator is a STIX Pattern as specified in STIX Version 2.0 Part 5 - STIX Patterning. ', + example: "[destination:ip = '91.219.29.188/32']\n", + name: 'misp.threat_indicator.attack_pattern', + type: 'keyword', + }, + 'misp.threat_indicator.attack_pattern_kql': { + category: 'misp', + description: + 'The attack_pattern for this indicator is KQL query that matches the attack_pattern specified in the STIX Pattern format. ', + example: 'destination.ip: "91.219.29.188/32"\n', + name: 'misp.threat_indicator.attack_pattern_kql', + type: 'keyword', + }, + 'misp.threat_indicator.negate': { + category: 'misp', + description: 'When set to true, it specifies the absence of the attack_pattern. ', + name: 'misp.threat_indicator.negate', + type: 'boolean', + }, + 'misp.threat_indicator.intrusion_set': { + category: 'misp', + description: 'Name of the intrusion set if known. ', + name: 'misp.threat_indicator.intrusion_set', + type: 'keyword', + }, + 'misp.threat_indicator.campaign': { + category: 'misp', + description: 'Name of the attack campaign if known. ', + name: 'misp.threat_indicator.campaign', + type: 'keyword', + }, + 'misp.threat_indicator.threat_actor': { + category: 'misp', + description: 'Name of the threat actor if known. ', + name: 'misp.threat_indicator.threat_actor', + type: 'keyword', + }, + 'misp.observed_data.id': { + category: 'misp', + description: 'Identifier of the Observed Data. ', + name: 'misp.observed_data.id', + type: 'keyword', + }, + 'misp.observed_data.first_observed': { + category: 'misp', + description: 'The beginning of the time window that the data was observed, in RFC3339 format. ', + name: 'misp.observed_data.first_observed', + type: 'date', + }, + 'misp.observed_data.last_observed': { + category: 'misp', + description: 'The end of the time window that the data was observed, in RFC3339 format. ', + name: 'misp.observed_data.last_observed', + type: 'date', + }, + 'misp.observed_data.number_observed': { + category: 'misp', + description: + 'The number of times the data represented in the objects property was observed. This MUST be an integer between 1 and 999,999,999 inclusive. ', + name: 'misp.observed_data.number_observed', + type: 'integer', + }, + 'misp.observed_data.objects': { + category: 'misp', + description: + 'A dictionary of Cyber Observable Objects that describes the single fact that was observed. ', + name: 'misp.observed_data.objects', + type: 'keyword', + }, + 'misp.report.id': { + category: 'misp', + description: 'Identifier of the Report. ', + name: 'misp.report.id', + type: 'keyword', + }, + 'misp.report.labels': { + category: 'misp', + description: + 'This field is an Open Vocabulary that specifies the primary subject of this report. Open Vocab - report-label-ov. threat-report,attack-pattern,campaign,identity,indicator,malware,observed-data,threat-actor,tool,vulnerability ', + name: 'misp.report.labels', + type: 'keyword', + }, + 'misp.report.name': { + category: 'misp', + description: 'The name used to identify the Report. ', + name: 'misp.report.name', + type: 'keyword', + }, + 'misp.report.description': { + category: 'misp', + description: 'A description that provides more details and context about Report. ', + name: 'misp.report.description', + type: 'text', + }, + 'misp.report.published': { + category: 'misp', + description: + 'The date that this report object was officially published by the creator of this report, in RFC3339 format. ', + name: 'misp.report.published', + type: 'date', + }, + 'misp.report.object_refs': { + category: 'misp', + description: 'Specifies the STIX Objects that are referred to by this Report. ', + name: 'misp.report.object_refs', + type: 'text', + }, + 'misp.threat_actor.id': { + category: 'misp', + description: 'Identifier of the Threat Actor. ', + name: 'misp.threat_actor.id', + type: 'keyword', + }, + 'misp.threat_actor.labels': { + category: 'misp', + description: + 'This field specifies the type of threat actor. Open Vocab - threat-actor-label-ov. activist,competitor,crime-syndicate,criminal,hacker,insider-accidental,insider-disgruntled,nation-state,sensationalist,spy,terrorist ', + name: 'misp.threat_actor.labels', + type: 'keyword', + }, + 'misp.threat_actor.name': { + category: 'misp', + description: 'The name used to identify this Threat Actor or Threat Actor group. ', + name: 'misp.threat_actor.name', + type: 'keyword', + }, + 'misp.threat_actor.description': { + category: 'misp', + description: 'A description that provides more details and context about the Threat Actor. ', + name: 'misp.threat_actor.description', + type: 'text', + }, + 'misp.threat_actor.aliases': { + category: 'misp', + description: 'A list of other names that this Threat Actor is believed to use. ', + name: 'misp.threat_actor.aliases', + type: 'text', + }, + 'misp.threat_actor.roles': { + category: 'misp', + description: + 'This is a list of roles the Threat Actor plays. Open Vocab - threat-actor-role-ov. agent,director,independent,sponsor,infrastructure-operator,infrastructure-architect,malware-author ', + name: 'misp.threat_actor.roles', + type: 'text', + }, + 'misp.threat_actor.goals': { + category: 'misp', + description: 'The high level goals of this Threat Actor, namely, what are they trying to do. ', + name: 'misp.threat_actor.goals', + type: 'text', + }, + 'misp.threat_actor.sophistication': { + category: 'misp', + description: + 'The skill, specific knowledge, special training, or expertise a Threat Actor must have to perform the attack. Open Vocab - threat-actor-sophistication-ov. none,minimal,intermediate,advanced,strategic,expert,innovator ', + name: 'misp.threat_actor.sophistication', + type: 'text', + }, + 'misp.threat_actor.resource_level': { + category: 'misp', + description: + 'This defines the organizational level at which this Threat Actor typically works. Open Vocab - attack-resource-level-ov. individual,club,contest,team,organization,government ', + name: 'misp.threat_actor.resource_level', + type: 'text', + }, + 'misp.threat_actor.primary_motivation': { + category: 'misp', + description: + 'The primary reason, motivation, or purpose behind this Threat Actor. Open Vocab - attack-motivation-ov. accidental,coercion,dominance,ideology,notoriety,organizational-gain,personal-gain,personal-satisfaction,revenge,unpredictable ', + name: 'misp.threat_actor.primary_motivation', + type: 'text', + }, + 'misp.threat_actor.secondary_motivations': { + category: 'misp', + description: + 'The secondary reasons, motivations, or purposes behind this Threat Actor. Open Vocab - attack-motivation-ov. accidental,coercion,dominance,ideology,notoriety,organizational-gain,personal-gain,personal-satisfaction,revenge,unpredictable ', + name: 'misp.threat_actor.secondary_motivations', + type: 'text', + }, + 'misp.threat_actor.personal_motivations': { + category: 'misp', + description: + 'The personal reasons, motivations, or purposes of the Threat Actor regardless of organizational goals. Open Vocab - attack-motivation-ov. accidental,coercion,dominance,ideology,notoriety,organizational-gain,personal-gain,personal-satisfaction,revenge,unpredictable ', + name: 'misp.threat_actor.personal_motivations', + type: 'text', + }, + 'misp.tool.id': { + category: 'misp', + description: 'Identifier of the Tool. ', + name: 'misp.tool.id', + type: 'keyword', + }, + 'misp.tool.labels': { + category: 'misp', + description: + 'The kind(s) of tool(s) being described. Open Vocab - tool-label-ov. denial-of-service,exploitation,information-gathering,network-capture,credential-exploitation,remote-access,vulnerability-scanning ', + name: 'misp.tool.labels', + type: 'keyword', + }, + 'misp.tool.name': { + category: 'misp', + description: 'The name used to identify the Tool. ', + name: 'misp.tool.name', + type: 'keyword', + }, + 'misp.tool.description': { + category: 'misp', + description: 'A description that provides more details and context about the Tool. ', + name: 'misp.tool.description', + type: 'text', + }, + 'misp.tool.tool_version': { + category: 'misp', + description: 'The version identifier associated with the Tool. ', + name: 'misp.tool.tool_version', + type: 'keyword', + }, + 'misp.tool.kill_chain_phases': { + category: 'misp', + description: 'The list of kill chain phases for which this Tool instance can be used. ', + name: 'misp.tool.kill_chain_phases', + type: 'text', + }, + 'misp.vulnerability.id': { + category: 'misp', + description: 'Identifier of the Vulnerability. ', + name: 'misp.vulnerability.id', + type: 'keyword', + }, + 'misp.vulnerability.name': { + category: 'misp', + description: 'The name used to identify the Vulnerability. ', + name: 'misp.vulnerability.name', + type: 'keyword', + }, + 'misp.vulnerability.description': { + category: 'misp', + description: 'A description that provides more details and context about the Vulnerability. ', + name: 'misp.vulnerability.description', + type: 'text', + }, + 'mssql.log.origin': { + category: 'mssql', + description: 'Origin of the message, usually the server but it can also be a recovery process', + name: 'mssql.log.origin', + type: 'keyword', + }, + 'o365.audit.Actor.ID': { + category: 'o365', + name: 'o365.audit.Actor.ID', + type: 'keyword', + }, + 'o365.audit.Actor.Type': { + category: 'o365', + name: 'o365.audit.Actor.Type', + type: 'keyword', + }, + 'o365.audit.ActorContextId': { + category: 'o365', + name: 'o365.audit.ActorContextId', + type: 'keyword', + }, + 'o365.audit.ActorIpAddress': { + category: 'o365', + name: 'o365.audit.ActorIpAddress', + type: 'keyword', + }, + 'o365.audit.ActorUserId': { + category: 'o365', + name: 'o365.audit.ActorUserId', + type: 'keyword', + }, + 'o365.audit.ActorYammerUserId': { + category: 'o365', + name: 'o365.audit.ActorYammerUserId', + type: 'keyword', + }, + 'o365.audit.AlertEntityId': { + category: 'o365', + name: 'o365.audit.AlertEntityId', + type: 'keyword', + }, + 'o365.audit.AlertId': { + category: 'o365', + name: 'o365.audit.AlertId', + type: 'keyword', + }, + 'o365.audit.AlertLinks': { + category: 'o365', + name: 'o365.audit.AlertLinks', + type: 'array', + }, + 'o365.audit.AlertType': { + category: 'o365', + name: 'o365.audit.AlertType', + type: 'keyword', + }, + 'o365.audit.AppId': { + category: 'o365', + name: 'o365.audit.AppId', + type: 'keyword', + }, + 'o365.audit.ApplicationDisplayName': { + category: 'o365', + name: 'o365.audit.ApplicationDisplayName', + type: 'keyword', + }, + 'o365.audit.ApplicationId': { + category: 'o365', + name: 'o365.audit.ApplicationId', + type: 'keyword', + }, + 'o365.audit.AzureActiveDirectoryEventType': { + category: 'o365', + name: 'o365.audit.AzureActiveDirectoryEventType', + type: 'keyword', + }, + 'o365.audit.ExchangeMetaData.*': { + category: 'o365', + name: 'o365.audit.ExchangeMetaData.*', + type: 'object', + }, + 'o365.audit.Category': { + category: 'o365', + name: 'o365.audit.Category', + type: 'keyword', + }, + 'o365.audit.ClientAppId': { + category: 'o365', + name: 'o365.audit.ClientAppId', + type: 'keyword', + }, + 'o365.audit.ClientInfoString': { + category: 'o365', + name: 'o365.audit.ClientInfoString', + type: 'keyword', + }, + 'o365.audit.ClientIP': { + category: 'o365', + name: 'o365.audit.ClientIP', + type: 'keyword', + }, + 'o365.audit.ClientIPAddress': { + category: 'o365', + name: 'o365.audit.ClientIPAddress', + type: 'keyword', + }, + 'o365.audit.Comments': { + category: 'o365', + name: 'o365.audit.Comments', + type: 'text', + }, + 'o365.audit.CorrelationId': { + category: 'o365', + name: 'o365.audit.CorrelationId', + type: 'keyword', + }, + 'o365.audit.CreationTime': { + category: 'o365', + name: 'o365.audit.CreationTime', + type: 'keyword', + }, + 'o365.audit.CustomUniqueId': { + category: 'o365', + name: 'o365.audit.CustomUniqueId', + type: 'keyword', + }, + 'o365.audit.Data': { + category: 'o365', + name: 'o365.audit.Data', + type: 'keyword', + }, + 'o365.audit.DataType': { + category: 'o365', + name: 'o365.audit.DataType', + type: 'keyword', + }, + 'o365.audit.EntityType': { + category: 'o365', + name: 'o365.audit.EntityType', + type: 'keyword', + }, + 'o365.audit.EventData': { + category: 'o365', + name: 'o365.audit.EventData', + type: 'keyword', + }, + 'o365.audit.EventSource': { + category: 'o365', + name: 'o365.audit.EventSource', + type: 'keyword', + }, + 'o365.audit.ExceptionInfo.*': { + category: 'o365', + name: 'o365.audit.ExceptionInfo.*', + type: 'object', + }, + 'o365.audit.ExtendedProperties.*': { + category: 'o365', + name: 'o365.audit.ExtendedProperties.*', + type: 'object', + }, + 'o365.audit.ExternalAccess': { + category: 'o365', + name: 'o365.audit.ExternalAccess', + type: 'keyword', + }, + 'o365.audit.GroupName': { + category: 'o365', + name: 'o365.audit.GroupName', + type: 'keyword', + }, + 'o365.audit.Id': { + category: 'o365', + name: 'o365.audit.Id', + type: 'keyword', + }, + 'o365.audit.ImplicitShare': { + category: 'o365', + name: 'o365.audit.ImplicitShare', + type: 'keyword', + }, + 'o365.audit.IncidentId': { + category: 'o365', + name: 'o365.audit.IncidentId', + type: 'keyword', + }, + 'o365.audit.InternalLogonType': { + category: 'o365', + name: 'o365.audit.InternalLogonType', + type: 'keyword', + }, + 'o365.audit.InterSystemsId': { + category: 'o365', + name: 'o365.audit.InterSystemsId', + type: 'keyword', + }, + 'o365.audit.IntraSystemId': { + category: 'o365', + name: 'o365.audit.IntraSystemId', + type: 'keyword', + }, + 'o365.audit.Item.*': { + category: 'o365', + name: 'o365.audit.Item.*', + type: 'object', + }, + 'o365.audit.Item.*.*': { + category: 'o365', + name: 'o365.audit.Item.*.*', + type: 'object', + }, + 'o365.audit.ItemName': { + category: 'o365', + name: 'o365.audit.ItemName', + type: 'keyword', + }, + 'o365.audit.ItemType': { + category: 'o365', + name: 'o365.audit.ItemType', + type: 'keyword', + }, + 'o365.audit.ListId': { + category: 'o365', + name: 'o365.audit.ListId', + type: 'keyword', + }, + 'o365.audit.ListItemUniqueId': { + category: 'o365', + name: 'o365.audit.ListItemUniqueId', + type: 'keyword', + }, + 'o365.audit.LogonError': { + category: 'o365', + name: 'o365.audit.LogonError', + type: 'keyword', + }, + 'o365.audit.LogonType': { + category: 'o365', + name: 'o365.audit.LogonType', + type: 'keyword', + }, + 'o365.audit.LogonUserSid': { + category: 'o365', + name: 'o365.audit.LogonUserSid', + type: 'keyword', + }, + 'o365.audit.MailboxGuid': { + category: 'o365', + name: 'o365.audit.MailboxGuid', + type: 'keyword', + }, + 'o365.audit.MailboxOwnerMasterAccountSid': { + category: 'o365', + name: 'o365.audit.MailboxOwnerMasterAccountSid', + type: 'keyword', + }, + 'o365.audit.MailboxOwnerSid': { + category: 'o365', + name: 'o365.audit.MailboxOwnerSid', + type: 'keyword', + }, + 'o365.audit.MailboxOwnerUPN': { + category: 'o365', + name: 'o365.audit.MailboxOwnerUPN', + type: 'keyword', + }, + 'o365.audit.Members': { + category: 'o365', + name: 'o365.audit.Members', + type: 'array', + }, + 'o365.audit.Members.*': { + category: 'o365', + name: 'o365.audit.Members.*', + type: 'object', + }, + 'o365.audit.ModifiedProperties.*.*': { + category: 'o365', + name: 'o365.audit.ModifiedProperties.*.*', + type: 'object', + }, + 'o365.audit.Name': { + category: 'o365', + name: 'o365.audit.Name', + type: 'keyword', + }, + 'o365.audit.ObjectId': { + category: 'o365', + name: 'o365.audit.ObjectId', + type: 'keyword', + }, + 'o365.audit.Operation': { + category: 'o365', + name: 'o365.audit.Operation', + type: 'keyword', + }, + 'o365.audit.OrganizationId': { + category: 'o365', + name: 'o365.audit.OrganizationId', + type: 'keyword', + }, + 'o365.audit.OrganizationName': { + category: 'o365', + name: 'o365.audit.OrganizationName', + type: 'keyword', + }, + 'o365.audit.OriginatingServer': { + category: 'o365', + name: 'o365.audit.OriginatingServer', + type: 'keyword', + }, + 'o365.audit.Parameters.*': { + category: 'o365', + name: 'o365.audit.Parameters.*', + type: 'object', + }, + 'o365.audit.PolicyDetails': { + category: 'o365', + name: 'o365.audit.PolicyDetails', + type: 'array', + }, + 'o365.audit.PolicyId': { + category: 'o365', + name: 'o365.audit.PolicyId', + type: 'keyword', + }, + 'o365.audit.RecordType': { + category: 'o365', + name: 'o365.audit.RecordType', + type: 'keyword', + }, + 'o365.audit.ResultStatus': { + category: 'o365', + name: 'o365.audit.ResultStatus', + type: 'keyword', + }, + 'o365.audit.SensitiveInfoDetectionIsIncluded': { + category: 'o365', + name: 'o365.audit.SensitiveInfoDetectionIsIncluded', + type: 'keyword', + }, + 'o365.audit.SharePointMetaData.*': { + category: 'o365', + name: 'o365.audit.SharePointMetaData.*', + type: 'object', + }, + 'o365.audit.SessionId': { + category: 'o365', + name: 'o365.audit.SessionId', + type: 'keyword', + }, + 'o365.audit.Severity': { + category: 'o365', + name: 'o365.audit.Severity', + type: 'keyword', + }, + 'o365.audit.Site': { + category: 'o365', + name: 'o365.audit.Site', + type: 'keyword', + }, + 'o365.audit.SiteUrl': { + category: 'o365', + name: 'o365.audit.SiteUrl', + type: 'keyword', + }, + 'o365.audit.Source': { + category: 'o365', + name: 'o365.audit.Source', + type: 'keyword', + }, + 'o365.audit.SourceFileExtension': { + category: 'o365', + name: 'o365.audit.SourceFileExtension', + type: 'keyword', + }, + 'o365.audit.SourceFileName': { + category: 'o365', + name: 'o365.audit.SourceFileName', + type: 'keyword', + }, + 'o365.audit.SourceRelativeUrl': { + category: 'o365', + name: 'o365.audit.SourceRelativeUrl', + type: 'keyword', + }, + 'o365.audit.Status': { + category: 'o365', + name: 'o365.audit.Status', + type: 'keyword', + }, + 'o365.audit.SupportTicketId': { + category: 'o365', + name: 'o365.audit.SupportTicketId', + type: 'keyword', + }, + 'o365.audit.Target.ID': { + category: 'o365', + name: 'o365.audit.Target.ID', + type: 'keyword', + }, + 'o365.audit.Target.Type': { + category: 'o365', + name: 'o365.audit.Target.Type', + type: 'keyword', + }, + 'o365.audit.TargetContextId': { + category: 'o365', + name: 'o365.audit.TargetContextId', + type: 'keyword', + }, + 'o365.audit.TargetUserOrGroupName': { + category: 'o365', + name: 'o365.audit.TargetUserOrGroupName', + type: 'keyword', + }, + 'o365.audit.TargetUserOrGroupType': { + category: 'o365', + name: 'o365.audit.TargetUserOrGroupType', + type: 'keyword', + }, + 'o365.audit.TeamName': { + category: 'o365', + name: 'o365.audit.TeamName', + type: 'keyword', + }, + 'o365.audit.TeamGuid': { + category: 'o365', + name: 'o365.audit.TeamGuid', + type: 'keyword', + }, + 'o365.audit.UniqueSharingId': { + category: 'o365', + name: 'o365.audit.UniqueSharingId', + type: 'keyword', + }, + 'o365.audit.UserAgent': { + category: 'o365', + name: 'o365.audit.UserAgent', + type: 'keyword', + }, + 'o365.audit.UserId': { + category: 'o365', + name: 'o365.audit.UserId', + type: 'keyword', + }, + 'o365.audit.UserKey': { + category: 'o365', + name: 'o365.audit.UserKey', + type: 'keyword', + }, + 'o365.audit.UserType': { + category: 'o365', + name: 'o365.audit.UserType', + type: 'keyword', + }, + 'o365.audit.Version': { + category: 'o365', + name: 'o365.audit.Version', + type: 'keyword', + }, + 'o365.audit.WebId': { + category: 'o365', + name: 'o365.audit.WebId', + type: 'keyword', + }, + 'o365.audit.Workload': { + category: 'o365', + name: 'o365.audit.Workload', + type: 'keyword', + }, + 'o365.audit.YammerNetworkId': { + category: 'o365', + name: 'o365.audit.YammerNetworkId', + type: 'keyword', + }, + 'okta.uuid': { + category: 'okta', + description: 'The unique identifier of the Okta LogEvent. ', + name: 'okta.uuid', + type: 'keyword', + }, + 'okta.event_type': { + category: 'okta', + description: 'The type of the LogEvent. ', + name: 'okta.event_type', + type: 'keyword', + }, + 'okta.version': { + category: 'okta', + description: 'The version of the LogEvent. ', + name: 'okta.version', + type: 'keyword', + }, + 'okta.severity': { + category: 'okta', + description: 'The severity of the LogEvent. Must be one of DEBUG, INFO, WARN, or ERROR. ', + name: 'okta.severity', + type: 'keyword', + }, + 'okta.display_message': { + category: 'okta', + description: 'The display message of the LogEvent. ', + name: 'okta.display_message', + type: 'keyword', + }, + 'okta.actor.id': { + category: 'okta', + description: 'Identifier of the actor. ', + name: 'okta.actor.id', + type: 'keyword', + }, + 'okta.actor.type': { + category: 'okta', + description: 'Type of the actor. ', + name: 'okta.actor.type', + type: 'keyword', + }, + 'okta.actor.alternate_id': { + category: 'okta', + description: 'Alternate identifier of the actor. ', + name: 'okta.actor.alternate_id', + type: 'keyword', + }, + 'okta.actor.display_name': { + category: 'okta', + description: 'Display name of the actor. ', + name: 'okta.actor.display_name', + type: 'keyword', + }, + 'okta.client.ip': { + category: 'okta', + description: 'The IP address of the client. ', + name: 'okta.client.ip', + type: 'ip', + }, + 'okta.client.user_agent.raw_user_agent': { + category: 'okta', + description: 'The raw informaton of the user agent. ', + name: 'okta.client.user_agent.raw_user_agent', + type: 'keyword', + }, + 'okta.client.user_agent.os': { + category: 'okta', + description: 'The OS informaton. ', + name: 'okta.client.user_agent.os', + type: 'keyword', + }, + 'okta.client.user_agent.browser': { + category: 'okta', + description: 'The browser informaton of the client. ', + name: 'okta.client.user_agent.browser', + type: 'keyword', + }, + 'okta.client.zone': { + category: 'okta', + description: 'The zone information of the client. ', + name: 'okta.client.zone', + type: 'keyword', + }, + 'okta.client.device': { + category: 'okta', + description: 'The information of the client device. ', + name: 'okta.client.device', + type: 'keyword', + }, + 'okta.client.id': { + category: 'okta', + description: 'The identifier of the client. ', + name: 'okta.client.id', + type: 'keyword', + }, + 'okta.outcome.reason': { + category: 'okta', + description: 'The reason of the outcome. ', + name: 'okta.outcome.reason', + type: 'keyword', + }, + 'okta.outcome.result': { + category: 'okta', + description: + 'The result of the outcome. Must be one of: SUCCESS, FAILURE, SKIPPED, ALLOW, DENY, CHALLENGE, UNKNOWN. ', + name: 'okta.outcome.result', + type: 'keyword', + }, + 'okta.target.id': { + category: 'okta', + description: 'Identifier of the actor. ', + name: 'okta.target.id', + type: 'keyword', + }, + 'okta.target.type': { + category: 'okta', + description: 'Type of the actor. ', + name: 'okta.target.type', + type: 'keyword', + }, + 'okta.target.alternate_id': { + category: 'okta', + description: 'Alternate identifier of the actor. ', + name: 'okta.target.alternate_id', + type: 'keyword', + }, + 'okta.target.display_name': { + category: 'okta', + description: 'Display name of the actor. ', + name: 'okta.target.display_name', + type: 'keyword', + }, + 'okta.transaction.id': { + category: 'okta', + description: 'Identifier of the transaction. ', + name: 'okta.transaction.id', + type: 'keyword', + }, + 'okta.transaction.type': { + category: 'okta', + description: 'The type of transaction. Must be one of "WEB", "JOB". ', + name: 'okta.transaction.type', + type: 'keyword', + }, + 'okta.debug_context.debug_data.device_fingerprint': { + category: 'okta', + description: 'The fingerprint of the device. ', + name: 'okta.debug_context.debug_data.device_fingerprint', + type: 'keyword', + }, + 'okta.debug_context.debug_data.request_id': { + category: 'okta', + description: 'The identifier of the request. ', + name: 'okta.debug_context.debug_data.request_id', + type: 'keyword', + }, + 'okta.debug_context.debug_data.request_uri': { + category: 'okta', + description: 'The request URI. ', + name: 'okta.debug_context.debug_data.request_uri', + type: 'keyword', + }, + 'okta.debug_context.debug_data.threat_suspected': { + category: 'okta', + description: 'Threat suspected. ', + name: 'okta.debug_context.debug_data.threat_suspected', + type: 'keyword', + }, + 'okta.debug_context.debug_data.url': { + category: 'okta', + description: 'The URL. ', + name: 'okta.debug_context.debug_data.url', + type: 'keyword', + }, + 'okta.authentication_context.authentication_provider': { + category: 'okta', + description: + 'The information about the authentication provider. Must be one of OKTA_AUTHENTICATION_PROVIDER, ACTIVE_DIRECTORY, LDAP, FEDERATION, SOCIAL, FACTOR_PROVIDER. ', + name: 'okta.authentication_context.authentication_provider', + type: 'keyword', + }, + 'okta.authentication_context.authentication_step': { + category: 'okta', + description: 'The authentication step. ', + name: 'okta.authentication_context.authentication_step', + type: 'integer', + }, + 'okta.authentication_context.credential_provider': { + category: 'okta', + description: + 'The information about credential provider. Must be one of OKTA_CREDENTIAL_PROVIDER, RSA, SYMANTEC, GOOGLE, DUO, YUBIKEY. ', + name: 'okta.authentication_context.credential_provider', + type: 'keyword', + }, + 'okta.authentication_context.credential_type': { + category: 'okta', + description: + 'The information about credential type. Must be one of OTP, SMS, PASSWORD, ASSERTION, IWA, EMAIL, OAUTH2, JWT, CERTIFICATE, PRE_SHARED_SYMMETRIC_KEY, OKTA_CLIENT_SESSION, DEVICE_UDID. ', + name: 'okta.authentication_context.credential_type', + type: 'keyword', + }, + 'okta.authentication_context.issuer.id': { + category: 'okta', + description: 'The identifier of the issuer. ', + name: 'okta.authentication_context.issuer.id', + type: 'keyword', + }, + 'okta.authentication_context.issuer.type': { + category: 'okta', + description: 'The type of the issuer. ', + name: 'okta.authentication_context.issuer.type', + type: 'keyword', + }, + 'okta.authentication_context.external_session_id': { + category: 'okta', + description: 'The session identifer of the external session if any. ', + name: 'okta.authentication_context.external_session_id', + type: 'keyword', + }, + 'okta.authentication_context.interface': { + category: 'okta', + description: 'The interface used. e.g., Outlook, Office365, wsTrust ', + name: 'okta.authentication_context.interface', + type: 'keyword', + }, + 'okta.security_context.as.number': { + category: 'okta', + description: 'The AS number. ', + name: 'okta.security_context.as.number', + type: 'integer', + }, + 'okta.security_context.as.organization.name': { + category: 'okta', + description: 'The organization name. ', + name: 'okta.security_context.as.organization.name', + type: 'keyword', + }, + 'okta.security_context.isp': { + category: 'okta', + description: 'The Internet Service Provider. ', + name: 'okta.security_context.isp', + type: 'keyword', + }, + 'okta.security_context.domain': { + category: 'okta', + description: 'The domain name. ', + name: 'okta.security_context.domain', + type: 'keyword', + }, + 'okta.security_context.is_proxy': { + category: 'okta', + description: 'Whether it is a proxy or not. ', + name: 'okta.security_context.is_proxy', + type: 'boolean', + }, + 'okta.request.ip_chain.ip': { + category: 'okta', + description: 'IP address. ', + name: 'okta.request.ip_chain.ip', + type: 'ip', + }, + 'okta.request.ip_chain.version': { + category: 'okta', + description: 'IP version. Must be one of V4, V6. ', + name: 'okta.request.ip_chain.version', + type: 'keyword', + }, + 'okta.request.ip_chain.source': { + category: 'okta', + description: 'Source information. ', + name: 'okta.request.ip_chain.source', + type: 'keyword', + }, + 'okta.request.ip_chain.geographical_context.city': { + category: 'okta', + description: 'The city.', + name: 'okta.request.ip_chain.geographical_context.city', + type: 'keyword', + }, + 'okta.request.ip_chain.geographical_context.state': { + category: 'okta', + description: 'The state.', + name: 'okta.request.ip_chain.geographical_context.state', + type: 'keyword', + }, + 'okta.request.ip_chain.geographical_context.postal_code': { + category: 'okta', + description: 'The postal code.', + name: 'okta.request.ip_chain.geographical_context.postal_code', + type: 'keyword', + }, + 'okta.request.ip_chain.geographical_context.country': { + category: 'okta', + description: 'The country.', + name: 'okta.request.ip_chain.geographical_context.country', + type: 'keyword', + }, + 'okta.request.ip_chain.geographical_context.geolocation': { + category: 'okta', + description: 'Geolocation information. ', + name: 'okta.request.ip_chain.geographical_context.geolocation', + type: 'geo_point', + }, + 'panw.panos.ruleset': { + category: 'panw', + description: 'Name of the rule that matched this session. ', + name: 'panw.panos.ruleset', + type: 'keyword', + }, + 'panw.panos.source.zone': { + category: 'panw', + description: 'Source zone for this session. ', + name: 'panw.panos.source.zone', + type: 'keyword', + }, + 'panw.panos.source.interface': { + category: 'panw', + description: 'Source interface for this session. ', + name: 'panw.panos.source.interface', + type: 'keyword', + }, + 'panw.panos.source.nat.ip': { + category: 'panw', + description: 'Post-NAT source IP. ', + name: 'panw.panos.source.nat.ip', + type: 'ip', + }, + 'panw.panos.source.nat.port': { + category: 'panw', + description: 'Post-NAT source port. ', + name: 'panw.panos.source.nat.port', + type: 'long', + }, + 'panw.panos.destination.zone': { + category: 'panw', + description: 'Destination zone for this session. ', + name: 'panw.panos.destination.zone', + type: 'keyword', + }, + 'panw.panos.destination.interface': { + category: 'panw', + description: 'Destination interface for this session. ', + name: 'panw.panos.destination.interface', + type: 'keyword', + }, + 'panw.panos.destination.nat.ip': { + category: 'panw', + description: 'Post-NAT destination IP. ', + name: 'panw.panos.destination.nat.ip', + type: 'ip', + }, + 'panw.panos.destination.nat.port': { + category: 'panw', + description: 'Post-NAT destination port. ', + name: 'panw.panos.destination.nat.port', + type: 'long', + }, + 'panw.panos.network.pcap_id': { + category: 'panw', + description: 'Packet capture ID for a threat. ', + name: 'panw.panos.network.pcap_id', + type: 'keyword', + }, + 'panw.panos.network.nat.community_id': { + category: 'panw', + description: 'Community ID flow-hash for the NAT 5-tuple. ', + name: 'panw.panos.network.nat.community_id', + type: 'keyword', + }, + 'panw.panos.file.hash': { + category: 'panw', + description: 'Binary hash for a threat file sent to be analyzed by the WildFire service. ', + name: 'panw.panos.file.hash', + type: 'keyword', + }, + 'panw.panos.url.category': { + category: 'panw', + description: + "For threat URLs, it's the URL category. For WildFire, the verdict on the file and is either 'malicious', 'grayware', or 'benign'. ", + name: 'panw.panos.url.category', + type: 'keyword', + }, + 'panw.panos.flow_id': { + category: 'panw', + description: 'Internal numeric identifier for each session. ', + name: 'panw.panos.flow_id', + type: 'keyword', + }, + 'panw.panos.sequence_number': { + category: 'panw', + description: + 'Log entry identifier that is incremented sequentially. Unique for each log type. ', + name: 'panw.panos.sequence_number', + type: 'long', + }, + 'panw.panos.threat.resource': { + category: 'panw', + description: 'URL or file name for a threat. ', + name: 'panw.panos.threat.resource', + type: 'keyword', + }, + 'panw.panos.threat.id': { + category: 'panw', + description: 'Palo Alto Networks identifier for the threat. ', + name: 'panw.panos.threat.id', + type: 'keyword', + }, + 'panw.panos.threat.name': { + category: 'panw', + description: 'Palo Alto Networks name for the threat. ', + name: 'panw.panos.threat.name', + type: 'keyword', + }, + 'panw.panos.action': { + category: 'panw', + description: 'Action taken for the session.', + name: 'panw.panos.action', + type: 'keyword', + }, + 'rabbitmq.log.pid': { + category: 'rabbitmq', + description: 'The Erlang process id', + example: '<0.222.0>', + name: 'rabbitmq.log.pid', + type: 'keyword', + }, + 'sophos.xg.device': { + category: 'sophos', + description: 'device ', + name: 'sophos.xg.device', + type: 'keyword', + }, + 'sophos.xg.date': { + category: 'sophos', + description: 'Date (yyyy-mm-dd) when the event occurred ', + name: 'sophos.xg.date', + type: 'date', + }, + 'sophos.xg.timezone': { + category: 'sophos', + description: 'Time (hh:mm:ss) when the event occurred ', + name: 'sophos.xg.timezone', + type: 'keyword', + }, + 'sophos.xg.device_name': { + category: 'sophos', + description: 'Model number of the device ', + name: 'sophos.xg.device_name', + type: 'keyword', + }, + 'sophos.xg.device_id': { + category: 'sophos', + description: 'Serial number of the device ', + name: 'sophos.xg.device_id', + type: 'keyword', + }, + 'sophos.xg.log_id': { + category: 'sophos', + description: 'Unique 12 characters code (0101011) ', + name: 'sophos.xg.log_id', + type: 'keyword', + }, + 'sophos.xg.log_type': { + category: 'sophos', + description: 'Type of event e.g. firewall event ', + name: 'sophos.xg.log_type', + type: 'keyword', + }, + 'sophos.xg.log_component': { + category: 'sophos', + description: 'Component responsible for logging e.g. Firewall rule ', + name: 'sophos.xg.log_component', + type: 'keyword', + }, + 'sophos.xg.log_subtype': { + category: 'sophos', + description: 'Sub type of event ', + name: 'sophos.xg.log_subtype', + type: 'keyword', + }, + 'sophos.xg.hb_health': { + category: 'sophos', + description: 'Heartbeat status ', + name: 'sophos.xg.hb_health', + type: 'keyword', + }, + 'sophos.xg.priority': { + category: 'sophos', + description: 'Severity level of traffic ', + name: 'sophos.xg.priority', + type: 'keyword', + }, + 'sophos.xg.status': { + category: 'sophos', + description: 'Ultimate status of traffic – Allowed or Denied ', + name: 'sophos.xg.status', + type: 'keyword', + }, + 'sophos.xg.duration': { + category: 'sophos', + description: 'Durability of traffic (seconds) ', + name: 'sophos.xg.duration', + type: 'long', + }, + 'sophos.xg.fw_rule_id': { + category: 'sophos', + description: 'Firewall Rule ID which is applied on the traffic ', + name: 'sophos.xg.fw_rule_id', + type: 'integer', + }, + 'sophos.xg.user_name': { + category: 'sophos', + description: 'user_name ', + name: 'sophos.xg.user_name', + type: 'keyword', + }, + 'sophos.xg.user_group': { + category: 'sophos', + description: 'Group name to which the user belongs ', + name: 'sophos.xg.user_group', + type: 'keyword', + }, + 'sophos.xg.iap': { + category: 'sophos', + description: 'Internet Access policy ID applied on the traffic ', + name: 'sophos.xg.iap', + type: 'keyword', + }, + 'sophos.xg.ips_policy_id': { + category: 'sophos', + description: 'IPS policy ID applied on the traffic ', + name: 'sophos.xg.ips_policy_id', + type: 'integer', + }, + 'sophos.xg.policy_type': { + category: 'sophos', + description: 'Policy type applied to the traffic ', + name: 'sophos.xg.policy_type', + type: 'keyword', + }, + 'sophos.xg.appfilter_policy_id': { + category: 'sophos', + description: 'Application Filter policy applied on the traffic ', + name: 'sophos.xg.appfilter_policy_id', + type: 'integer', + }, + 'sophos.xg.application_filter_policy': { + category: 'sophos', + description: 'Application Filter policy applied on the traffic ', + name: 'sophos.xg.application_filter_policy', + type: 'integer', + }, + 'sophos.xg.application': { + category: 'sophos', + description: 'Application name ', + name: 'sophos.xg.application', + type: 'keyword', + }, + 'sophos.xg.application_name': { + category: 'sophos', + description: 'Application name ', + name: 'sophos.xg.application_name', + type: 'keyword', + }, + 'sophos.xg.application_risk': { + category: 'sophos', + description: 'Risk level assigned to the application ', + name: 'sophos.xg.application_risk', + type: 'keyword', + }, + 'sophos.xg.application_technology': { + category: 'sophos', + description: 'Technology of the application ', + name: 'sophos.xg.application_technology', + type: 'keyword', + }, + 'sophos.xg.application_category': { + category: 'sophos', + description: 'Application is resolved by signature or synchronized application ', + name: 'sophos.xg.application_category', + type: 'keyword', + }, + 'sophos.xg.appresolvedby': { + category: 'sophos', + description: 'Technology of the application ', + name: 'sophos.xg.appresolvedby', + type: 'keyword', + }, + 'sophos.xg.app_is_cloud': { + category: 'sophos', + description: 'Application is Cloud ', + name: 'sophos.xg.app_is_cloud', + type: 'keyword', + }, + 'sophos.xg.in_interface': { + category: 'sophos', + description: 'Interface for incoming traffic, e.g., Port A ', + name: 'sophos.xg.in_interface', + type: 'keyword', + }, + 'sophos.xg.out_interface': { + category: 'sophos', + description: 'Interface for outgoing traffic, e.g., Port B ', + name: 'sophos.xg.out_interface', + type: 'keyword', + }, + 'sophos.xg.src_ip': { + category: 'sophos', + description: 'Original source IP address of traffic ', + name: 'sophos.xg.src_ip', + type: 'ip', + }, + 'sophos.xg.src_mac': { + category: 'sophos', + description: 'Original source MAC address of traffic ', + name: 'sophos.xg.src_mac', + type: 'keyword', + }, + 'sophos.xg.src_country_code': { + category: 'sophos', + description: 'Code of the country to which the source IP belongs ', + name: 'sophos.xg.src_country_code', + type: 'keyword', + }, + 'sophos.xg.dst_ip': { + category: 'sophos', + description: 'Original destination IP address of traffic ', + name: 'sophos.xg.dst_ip', + type: 'ip', + }, + 'sophos.xg.dst_country_code': { + category: 'sophos', + description: 'Code of the country to which the destination IP belongs ', + name: 'sophos.xg.dst_country_code', + type: 'keyword', + }, + 'sophos.xg.protocol': { + category: 'sophos', + description: 'Protocol number of traffic ', + name: 'sophos.xg.protocol', + type: 'keyword', + }, + 'sophos.xg.src_port': { + category: 'sophos', + description: 'Original source port of TCP and UDP traffic ', + name: 'sophos.xg.src_port', + type: 'integer', + }, + 'sophos.xg.dst_port': { + category: 'sophos', + description: 'Original destination port of TCP and UDP traffic ', + name: 'sophos.xg.dst_port', + type: 'integer', + }, + 'sophos.xg.icmp_type': { + category: 'sophos', + description: 'ICMP type of ICMP traffic ', + name: 'sophos.xg.icmp_type', + type: 'keyword', + }, + 'sophos.xg.icmp_code': { + category: 'sophos', + description: 'ICMP code of ICMP traffic ', + name: 'sophos.xg.icmp_code', + type: 'keyword', + }, + 'sophos.xg.sent_pkts': { + category: 'sophos', + description: 'Total number of packets sent ', + name: 'sophos.xg.sent_pkts', + type: 'long', + }, + 'sophos.xg.received_pkts': { + category: 'sophos', + description: 'Total number of packets received ', + name: 'sophos.xg.received_pkts', + type: 'long', + }, + 'sophos.xg.sent_bytes': { + category: 'sophos', + description: 'Total number of bytes sent ', + name: 'sophos.xg.sent_bytes', + type: 'long', + }, + 'sophos.xg.recv_bytes': { + category: 'sophos', + description: 'Total number of bytes received ', + name: 'sophos.xg.recv_bytes', + type: 'long', + }, + 'sophos.xg.trans_src_ ip': { + category: 'sophos', + description: 'Translated source IP address for outgoing traffic ', + name: 'sophos.xg.trans_src_ ip', + type: 'ip', + }, + 'sophos.xg.trans_src_port': { + category: 'sophos', + description: 'Translated source port for outgoing traffic ', + name: 'sophos.xg.trans_src_port', + type: 'integer', + }, + 'sophos.xg.trans_dst_ip': { + category: 'sophos', + description: 'Translated destination IP address for outgoing traffic ', + name: 'sophos.xg.trans_dst_ip', + type: 'ip', + }, + 'sophos.xg.trans_dst_port': { + category: 'sophos', + description: 'Translated destination port for outgoing traffic ', + name: 'sophos.xg.trans_dst_port', + type: 'integer', + }, + 'sophos.xg.srczonetype': { + category: 'sophos', + description: 'Type of source zone, e.g., LAN ', + name: 'sophos.xg.srczonetype', + type: 'keyword', + }, + 'sophos.xg.srczone': { + category: 'sophos', + description: 'Name of source zone ', + name: 'sophos.xg.srczone', + type: 'keyword', + }, + 'sophos.xg.dstzonetype': { + category: 'sophos', + description: 'Type of destination zone, e.g., WAN ', + name: 'sophos.xg.dstzonetype', + type: 'keyword', + }, + 'sophos.xg.dstzone': { + category: 'sophos', + description: 'Name of destination zone ', + name: 'sophos.xg.dstzone', + type: 'keyword', + }, + 'sophos.xg.dir_disp': { + category: 'sophos', + description: 'TPacket direction. Possible values:“org”, “reply”, “” ', + name: 'sophos.xg.dir_disp', + type: 'keyword', + }, + 'sophos.xg.connevent': { + category: 'sophos', + description: 'Event on which this log is generated ', + name: 'sophos.xg.connevent', + type: 'keyword', + }, + 'sophos.xg.conn_id': { + category: 'sophos', + description: 'Unique identifier of connection ', + name: 'sophos.xg.conn_id', + type: 'integer', + }, + 'sophos.xg.vconn_id': { + category: 'sophos', + description: 'Connection ID of the master connection ', + name: 'sophos.xg.vconn_id', + type: 'integer', + }, + 'sophos.xg.idp_policy_id': { + category: 'sophos', + description: 'IPS policy ID which is applied on the traffic ', + name: 'sophos.xg.idp_policy_id', + type: 'integer', + }, + 'sophos.xg.idp_policy_name': { + category: 'sophos', + description: 'IPS policy name i.e. IPS policy name which is applied on the traffic ', + name: 'sophos.xg.idp_policy_name', + type: 'keyword', + }, + 'sophos.xg.signature_id': { + category: 'sophos', + description: 'Signature ID ', + name: 'sophos.xg.signature_id', + type: 'keyword', + }, + 'sophos.xg.signature_msg': { + category: 'sophos', + description: 'Signature messsage ', + name: 'sophos.xg.signature_msg', + type: 'keyword', + }, + 'sophos.xg.classification': { + category: 'sophos', + description: 'Signature classification ', + name: 'sophos.xg.classification', + type: 'keyword', + }, + 'sophos.xg.rule_priority': { + category: 'sophos', + description: 'Priority of IPS policy ', + name: 'sophos.xg.rule_priority', + type: 'keyword', + }, + 'sophos.xg.platform': { + category: 'sophos', + description: 'Platform of the traffic. ', + name: 'sophos.xg.platform', + type: 'keyword', + }, + 'sophos.xg.category': { + category: 'sophos', + description: 'IPS signature category. ', + name: 'sophos.xg.category', + type: 'keyword', + }, + 'sophos.xg.target': { + category: 'sophos', + description: 'Platform of the traffic. ', + name: 'sophos.xg.target', + type: 'keyword', + }, + 'sophos.xg.eventid': { + category: 'sophos', + description: 'ATP Evenet ID ', + name: 'sophos.xg.eventid', + type: 'keyword', + }, + 'sophos.xg.ep_uuid': { + category: 'sophos', + description: 'Endpoint UUID ', + name: 'sophos.xg.ep_uuid', + type: 'keyword', + }, + 'sophos.xg.threatname': { + category: 'sophos', + description: 'ATP threatname ', + name: 'sophos.xg.threatname', + type: 'keyword', + }, + 'sophos.xg.sourceip': { + category: 'sophos', + description: 'Original source IP address of traffic ', + name: 'sophos.xg.sourceip', + type: 'ip', + }, + 'sophos.xg.destinationip': { + category: 'sophos', + description: 'Original destination IP address of traffic ', + name: 'sophos.xg.destinationip', + type: 'ip', + }, + 'sophos.xg.login_user': { + category: 'sophos', + description: 'ATP login user ', + name: 'sophos.xg.login_user', + type: 'keyword', + }, + 'sophos.xg.eventtype': { + category: 'sophos', + description: 'ATP event type ', + name: 'sophos.xg.eventtype', + type: 'keyword', + }, + 'sophos.xg.execution_path': { + category: 'sophos', + description: 'ATP execution path ', + name: 'sophos.xg.execution_path', + type: 'keyword', + }, + 'sophos.xg.av_policy_name': { + category: 'sophos', + description: 'Malware scanning policy name which is applied on the traffic ', + name: 'sophos.xg.av_policy_name', + type: 'keyword', + }, + 'sophos.xg.from_email_address': { + category: 'sophos', + description: 'Sender email address ', + name: 'sophos.xg.from_email_address', + type: 'keyword', + }, + 'sophos.xg.to_email_address': { + category: 'sophos', + description: 'Receipeint email address ', + name: 'sophos.xg.to_email_address', + type: 'keyword', + }, + 'sophos.xg.subject': { + category: 'sophos', + description: 'Email subject ', + name: 'sophos.xg.subject', + type: 'keyword', + }, + 'sophos.xg.mailsize': { + category: 'sophos', + description: 'mailsize ', + name: 'sophos.xg.mailsize', + type: 'integer', + }, + 'sophos.xg.virus': { + category: 'sophos', + description: 'virus name ', + name: 'sophos.xg.virus', + type: 'keyword', + }, + 'sophos.xg.FTP_url': { + category: 'sophos', + description: 'FTP URL from which virus was downloaded ', + name: 'sophos.xg.FTP_url', + type: 'keyword', + }, + 'sophos.xg.FTP_direction': { + category: 'sophos', + description: 'Direction of FTP transfer: Upload or Download ', + name: 'sophos.xg.FTP_direction', + type: 'keyword', + }, + 'sophos.xg.filesize': { + category: 'sophos', + description: 'Size of the file that contained virus ', + name: 'sophos.xg.filesize', + type: 'integer', + }, + 'sophos.xg.filepath': { + category: 'sophos', + description: 'Path of the file containing virus ', + name: 'sophos.xg.filepath', + type: 'keyword', + }, + 'sophos.xg.filename': { + category: 'sophos', + description: 'File name associated with the event ', + name: 'sophos.xg.filename', + type: 'keyword', + }, + 'sophos.xg.ftpcommand': { + category: 'sophos', + description: 'FTP command used when virus was found ', + name: 'sophos.xg.ftpcommand', + type: 'keyword', + }, + 'sophos.xg.url': { + category: 'sophos', + description: 'URL from which virus was downloaded ', + name: 'sophos.xg.url', + type: 'keyword', + }, + 'sophos.xg.domainname': { + category: 'sophos', + description: 'Domain from which virus was downloaded ', + name: 'sophos.xg.domainname', + type: 'keyword', + }, + 'sophos.xg.quarantine': { + category: 'sophos', + description: 'Path and filename of the file quarantined ', + name: 'sophos.xg.quarantine', + type: 'keyword', + }, + 'sophos.xg.src_domainname': { + category: 'sophos', + description: 'Sender domain name ', + name: 'sophos.xg.src_domainname', + type: 'keyword', + }, + 'sophos.xg.dst_domainname': { + category: 'sophos', + description: 'Receiver domain name ', + name: 'sophos.xg.dst_domainname', + type: 'keyword', + }, + 'sophos.xg.reason': { + category: 'sophos', + description: 'Reason why the record was detected as spam/malicious ', + name: 'sophos.xg.reason', + type: 'keyword', + }, + 'sophos.xg.referer': { + category: 'sophos', + description: 'Referer ', + name: 'sophos.xg.referer', + type: 'keyword', + }, + 'sophos.xg.spamaction': { + category: 'sophos', + description: 'Spam Action ', + name: 'sophos.xg.spamaction', + type: 'keyword', + }, + 'sophos.xg.mailid': { + category: 'sophos', + description: 'mailid ', + name: 'sophos.xg.mailid', + type: 'keyword', + }, + 'sophos.xg.quarantine_reason': { + category: 'sophos', + description: 'Quarantine reason ', + name: 'sophos.xg.quarantine_reason', + type: 'keyword', + }, + 'sophos.xg.status_code': { + category: 'sophos', + description: 'Status code ', + name: 'sophos.xg.status_code', + type: 'keyword', + }, + 'sophos.xg.override_token': { + category: 'sophos', + description: 'Override token ', + name: 'sophos.xg.override_token', + type: 'keyword', + }, + 'sophos.xg.con_id': { + category: 'sophos', + description: 'Unique identifier of connection ', + name: 'sophos.xg.con_id', + type: 'integer', + }, + 'sophos.xg.override_authorizer': { + category: 'sophos', + description: 'Override authorizer ', + name: 'sophos.xg.override_authorizer', + type: 'keyword', + }, + 'sophos.xg.transactionid': { + category: 'sophos', + description: 'Transaction ID of the AV scan. ', + name: 'sophos.xg.transactionid', + type: 'keyword', + }, + 'sophos.xg.upload_file_type': { + category: 'sophos', + description: 'Upload file type ', + name: 'sophos.xg.upload_file_type', + type: 'keyword', + }, + 'sophos.xg.upload_file_name': { + category: 'sophos', + description: 'Upload file name ', + name: 'sophos.xg.upload_file_name', + type: 'keyword', + }, + 'sophos.xg.httpresponsecode': { + category: 'sophos', + description: 'code of HTTP response ', + name: 'sophos.xg.httpresponsecode', + type: 'long', + }, + 'sophos.xg.user_gp': { + category: 'sophos', + description: 'Group name to which the user belongs. ', + name: 'sophos.xg.user_gp', + type: 'keyword', + }, + 'sophos.xg.category_type': { + category: 'sophos', + description: 'Type of category under which website falls ', + name: 'sophos.xg.category_type', + type: 'keyword', + }, + 'sophos.xg.download_file_type': { + category: 'sophos', + description: 'Download file type ', + name: 'sophos.xg.download_file_type', + type: 'keyword', + }, + 'sophos.xg.exceptions': { + category: 'sophos', + description: 'List of the checks excluded by web exceptions. ', + name: 'sophos.xg.exceptions', + type: 'keyword', + }, + 'sophos.xg.contenttype': { + category: 'sophos', + description: 'Type of the content ', + name: 'sophos.xg.contenttype', + type: 'keyword', + }, + 'sophos.xg.override_name': { + category: 'sophos', + description: 'Override name ', + name: 'sophos.xg.override_name', + type: 'keyword', + }, + 'sophos.xg.activityname': { + category: 'sophos', + description: 'Web policy activity that matched and caused the policy result. ', + name: 'sophos.xg.activityname', + type: 'keyword', + }, + 'sophos.xg.download_file_name': { + category: 'sophos', + description: 'Download file name ', + name: 'sophos.xg.download_file_name', + type: 'keyword', + }, + 'sophos.xg.sha1sum': { + category: 'sophos', + description: 'SHA1 checksum of the item being analyzed ', + name: 'sophos.xg.sha1sum', + type: 'keyword', + }, + 'sophos.xg.message_id': { + category: 'sophos', + description: 'Message ID ', + name: 'sophos.xg.message_id', + type: 'keyword', + }, + 'sophos.xg.connid': { + category: 'sophos', + description: 'Connection ID ', + name: 'sophos.xg.connid', + type: 'keyword', + }, + 'sophos.xg.message': { + category: 'sophos', + description: 'Message ', + name: 'sophos.xg.message', + type: 'keyword', + }, + 'sophos.xg.email_subject': { + category: 'sophos', + description: 'Email Subject ', + name: 'sophos.xg.email_subject', + type: 'keyword', + }, + 'sophos.xg.file_path': { + category: 'sophos', + description: 'File path ', + name: 'sophos.xg.file_path', + type: 'keyword', + }, + 'sophos.xg.dstdomain': { + category: 'sophos', + description: 'Destination Domain ', + name: 'sophos.xg.dstdomain', + type: 'keyword', + }, + 'sophos.xg.file_size': { + category: 'sophos', + description: 'File Size ', + name: 'sophos.xg.file_size', + type: 'integer', + }, + 'sophos.xg.transaction_id': { + category: 'sophos', + description: 'Transaction ID ', + name: 'sophos.xg.transaction_id', + type: 'keyword', + }, + 'sophos.xg.website': { + category: 'sophos', + description: 'Website ', + name: 'sophos.xg.website', + type: 'keyword', + }, + 'sophos.xg.file_name': { + category: 'sophos', + description: 'Filename ', + name: 'sophos.xg.file_name', + type: 'keyword', + }, + 'sophos.xg.context_prefix': { + category: 'sophos', + description: 'Content Prefix ', + name: 'sophos.xg.context_prefix', + type: 'keyword', + }, + 'sophos.xg.site_category': { + category: 'sophos', + description: 'Site Category ', + name: 'sophos.xg.site_category', + type: 'keyword', + }, + 'sophos.xg.context_suffix': { + category: 'sophos', + description: 'Context Suffix ', + name: 'sophos.xg.context_suffix', + type: 'keyword', + }, + 'sophos.xg.dictionary_name': { + category: 'sophos', + description: 'Dictionary Name ', + name: 'sophos.xg.dictionary_name', + type: 'keyword', + }, + 'sophos.xg.action': { + category: 'sophos', + description: 'Event Action ', + name: 'sophos.xg.action', + type: 'keyword', + }, + 'sophos.xg.user': { + category: 'sophos', + description: 'User ', + name: 'sophos.xg.user', + type: 'keyword', + }, + 'sophos.xg.context_match': { + category: 'sophos', + description: 'Context Match ', + name: 'sophos.xg.context_match', + type: 'keyword', + }, + 'sophos.xg.direction': { + category: 'sophos', + description: 'Direction ', + name: 'sophos.xg.direction', + type: 'keyword', + }, + 'sophos.xg.auth_client': { + category: 'sophos', + description: 'Auth Client ', + name: 'sophos.xg.auth_client', + type: 'keyword', + }, + 'sophos.xg.auth_mechanism': { + category: 'sophos', + description: 'Auth mechanism ', + name: 'sophos.xg.auth_mechanism', + type: 'keyword', + }, + 'sophos.xg.connectionname': { + category: 'sophos', + description: 'Connectionname ', + name: 'sophos.xg.connectionname', + type: 'keyword', + }, + 'sophos.xg.remotenetwork': { + category: 'sophos', + description: 'remotenetwork ', + name: 'sophos.xg.remotenetwork', + type: 'keyword', + }, + 'sophos.xg.localgateway': { + category: 'sophos', + description: 'Localgateway ', + name: 'sophos.xg.localgateway', + type: 'keyword', + }, + 'sophos.xg.localnetwork': { + category: 'sophos', + description: 'Localnetwork ', + name: 'sophos.xg.localnetwork', + type: 'keyword', + }, + 'sophos.xg.connectiontype': { + category: 'sophos', + description: 'Connectiontype ', + name: 'sophos.xg.connectiontype', + type: 'keyword', + }, + 'sophos.xg.oldversion': { + category: 'sophos', + description: 'Oldversion ', + name: 'sophos.xg.oldversion', + type: 'keyword', + }, + 'sophos.xg.newversion': { + category: 'sophos', + description: 'Newversion ', + name: 'sophos.xg.newversion', + type: 'keyword', + }, + 'sophos.xg.ipaddress': { + category: 'sophos', + description: 'Ipaddress ', + name: 'sophos.xg.ipaddress', + type: 'keyword', + }, + 'sophos.xg.client_physical_address': { + category: 'sophos', + description: 'Client physical address ', + name: 'sophos.xg.client_physical_address', + type: 'keyword', + }, + 'sophos.xg.client_host_name': { + category: 'sophos', + description: 'Client host name ', + name: 'sophos.xg.client_host_name', + type: 'keyword', + }, + 'sophos.xg.raw_data': { + category: 'sophos', + description: 'Raw data ', + name: 'sophos.xg.raw_data', + type: 'keyword', + }, + 'sophos.xg.Mode': { + category: 'sophos', + description: 'Mode ', + name: 'sophos.xg.Mode', + type: 'keyword', + }, + 'sophos.xg.sessionid': { + category: 'sophos', + description: 'Sessionid ', + name: 'sophos.xg.sessionid', + type: 'keyword', + }, + 'sophos.xg.starttime': { + category: 'sophos', + description: 'Starttime ', + name: 'sophos.xg.starttime', + type: 'date', + }, + 'sophos.xg.remote_ip': { + category: 'sophos', + description: 'Remote IP ', + name: 'sophos.xg.remote_ip', + type: 'ip', + }, + 'sophos.xg.timestamp': { + category: 'sophos', + description: 'timestamp ', + name: 'sophos.xg.timestamp', + type: 'date', + }, + 'sophos.xg.SysLog_SERVER_NAME': { + category: 'sophos', + description: 'SysLog SERVER NAME ', + name: 'sophos.xg.SysLog_SERVER_NAME', + type: 'keyword', + }, + 'sophos.xg.backup_mode': { + category: 'sophos', + description: 'Backup mode ', + name: 'sophos.xg.backup_mode', + type: 'keyword', + }, + 'sophos.xg.source': { + category: 'sophos', + description: 'Source ', + name: 'sophos.xg.source', + type: 'keyword', + }, + 'sophos.xg.server': { + category: 'sophos', + description: 'Server ', + name: 'sophos.xg.server', + type: 'keyword', + }, + 'sophos.xg.host': { + category: 'sophos', + description: 'Host ', + name: 'sophos.xg.host', + type: 'keyword', + }, + 'sophos.xg.responsetime': { + category: 'sophos', + description: 'Responsetime ', + name: 'sophos.xg.responsetime', + type: 'long', + }, + 'sophos.xg.cookie': { + category: 'sophos', + description: 'cookie ', + name: 'sophos.xg.cookie', + type: 'keyword', + }, + 'sophos.xg.querystring': { + category: 'sophos', + description: 'querystring ', + name: 'sophos.xg.querystring', + type: 'keyword', + }, + 'sophos.xg.extra': { + category: 'sophos', + description: 'extra ', + name: 'sophos.xg.extra', + type: 'keyword', + }, + 'sophos.xg.PHPSESSID': { + category: 'sophos', + description: 'PHPSESSID ', + name: 'sophos.xg.PHPSESSID', + type: 'keyword', + }, + 'sophos.xg.start_time': { + category: 'sophos', + description: 'Start time ', + name: 'sophos.xg.start_time', + type: 'date', + }, + 'sophos.xg.eventtime': { + category: 'sophos', + description: 'Event time ', + name: 'sophos.xg.eventtime', + type: 'date', + }, + 'sophos.xg.red_id': { + category: 'sophos', + description: 'RED ID ', + name: 'sophos.xg.red_id', + type: 'keyword', + }, + 'sophos.xg.branch_name': { + category: 'sophos', + description: 'Branch Name ', + name: 'sophos.xg.branch_name', + type: 'keyword', + }, + 'sophos.xg.updatedip': { + category: 'sophos', + description: 'updatedip ', + name: 'sophos.xg.updatedip', + type: 'ip', + }, + 'sophos.xg.idle_cpu': { + category: 'sophos', + description: 'idle ## ', + name: 'sophos.xg.idle_cpu', + type: 'float', + }, + 'sophos.xg.system_cpu': { + category: 'sophos', + description: 'system ', + name: 'sophos.xg.system_cpu', + type: 'float', + }, + 'sophos.xg.user_cpu': { + category: 'sophos', + description: 'system ', + name: 'sophos.xg.user_cpu', + type: 'float', + }, + 'sophos.xg.used': { + category: 'sophos', + description: 'used ', + name: 'sophos.xg.used', + type: 'integer', + }, + 'sophos.xg.unit': { + category: 'sophos', + description: 'unit ', + name: 'sophos.xg.unit', + type: 'keyword', + }, + 'sophos.xg.total_memory': { + category: 'sophos', + description: 'Total Memory ', + name: 'sophos.xg.total_memory', + type: 'integer', + }, + 'sophos.xg.free': { + category: 'sophos', + description: 'free ', + name: 'sophos.xg.free', + type: 'integer', + }, + 'sophos.xg.transmittederrors': { + category: 'sophos', + description: 'transmitted errors ', + name: 'sophos.xg.transmittederrors', + type: 'keyword', + }, + 'sophos.xg.receivederrors': { + category: 'sophos', + description: 'received errors ', + name: 'sophos.xg.receivederrors', + type: 'keyword', + }, + 'sophos.xg.receivedkbits': { + category: 'sophos', + description: 'received kbits ', + name: 'sophos.xg.receivedkbits', + type: 'long', + }, + 'sophos.xg.transmittedkbits': { + category: 'sophos', + description: 'transmitted kbits ', + name: 'sophos.xg.transmittedkbits', + type: 'long', + }, + 'sophos.xg.transmitteddrops': { + category: 'sophos', + description: 'transmitted drops ', + name: 'sophos.xg.transmitteddrops', + type: 'long', + }, + 'sophos.xg.receiveddrops': { + category: 'sophos', + description: 'received drops ', + name: 'sophos.xg.receiveddrops', + type: 'long', + }, + 'sophos.xg.collisions': { + category: 'sophos', + description: 'collisions ', + name: 'sophos.xg.collisions', + type: 'long', + }, + 'sophos.xg.interface': { + category: 'sophos', + description: 'interface ', + name: 'sophos.xg.interface', + type: 'keyword', + }, + 'sophos.xg.Configuration': { + category: 'sophos', + description: 'Configuration ', + name: 'sophos.xg.Configuration', + type: 'float', + }, + 'sophos.xg.Reports': { + category: 'sophos', + description: 'Reports ', + name: 'sophos.xg.Reports', + type: 'float', + }, + 'sophos.xg.Signature': { + category: 'sophos', + description: 'Signature ', + name: 'sophos.xg.Signature', + type: 'float', + }, + 'sophos.xg.Temp': { + category: 'sophos', + description: 'Temp ', + name: 'sophos.xg.Temp', + type: 'float', + }, + 'sophos.xg.users': { + category: 'sophos', + description: 'users ', + name: 'sophos.xg.users', + type: 'keyword', + }, + 'sophos.xg.ssid': { + category: 'sophos', + description: 'ssid ', + name: 'sophos.xg.ssid', + type: 'keyword', + }, + 'sophos.xg.ap': { + category: 'sophos', + description: 'ap ', + name: 'sophos.xg.ap', + type: 'keyword', + }, + 'sophos.xg.clients_conn_ssid': { + category: 'sophos', + description: 'clients connection ssid ', + name: 'sophos.xg.clients_conn_ssid', + type: 'keyword', + }, + 'suricata.eve.event_type': { + category: 'suricata', + name: 'suricata.eve.event_type', + type: 'keyword', + }, + 'suricata.eve.app_proto_orig': { + category: 'suricata', + name: 'suricata.eve.app_proto_orig', + type: 'keyword', + }, + 'suricata.eve.tcp.tcp_flags': { + category: 'suricata', + name: 'suricata.eve.tcp.tcp_flags', + type: 'keyword', + }, + 'suricata.eve.tcp.psh': { + category: 'suricata', + name: 'suricata.eve.tcp.psh', + type: 'boolean', + }, + 'suricata.eve.tcp.tcp_flags_tc': { + category: 'suricata', + name: 'suricata.eve.tcp.tcp_flags_tc', + type: 'keyword', + }, + 'suricata.eve.tcp.ack': { + category: 'suricata', + name: 'suricata.eve.tcp.ack', + type: 'boolean', + }, + 'suricata.eve.tcp.syn': { + category: 'suricata', + name: 'suricata.eve.tcp.syn', + type: 'boolean', + }, + 'suricata.eve.tcp.state': { + category: 'suricata', + name: 'suricata.eve.tcp.state', + type: 'keyword', + }, + 'suricata.eve.tcp.tcp_flags_ts': { + category: 'suricata', + name: 'suricata.eve.tcp.tcp_flags_ts', + type: 'keyword', + }, + 'suricata.eve.tcp.rst': { + category: 'suricata', + name: 'suricata.eve.tcp.rst', + type: 'boolean', + }, + 'suricata.eve.tcp.fin': { + category: 'suricata', + name: 'suricata.eve.tcp.fin', + type: 'boolean', + }, + 'suricata.eve.fileinfo.sha1': { + category: 'suricata', + name: 'suricata.eve.fileinfo.sha1', + type: 'keyword', + }, + 'suricata.eve.fileinfo.filename': { + category: 'suricata', + name: 'suricata.eve.fileinfo.filename', + type: 'alias', + }, + 'suricata.eve.fileinfo.tx_id': { + category: 'suricata', + name: 'suricata.eve.fileinfo.tx_id', + type: 'long', + }, + 'suricata.eve.fileinfo.state': { + category: 'suricata', + name: 'suricata.eve.fileinfo.state', + type: 'keyword', + }, + 'suricata.eve.fileinfo.stored': { + category: 'suricata', + name: 'suricata.eve.fileinfo.stored', + type: 'boolean', + }, + 'suricata.eve.fileinfo.gaps': { + category: 'suricata', + name: 'suricata.eve.fileinfo.gaps', + type: 'boolean', + }, + 'suricata.eve.fileinfo.sha256': { + category: 'suricata', + name: 'suricata.eve.fileinfo.sha256', + type: 'keyword', + }, + 'suricata.eve.fileinfo.md5': { + category: 'suricata', + name: 'suricata.eve.fileinfo.md5', + type: 'keyword', + }, + 'suricata.eve.fileinfo.size': { + category: 'suricata', + name: 'suricata.eve.fileinfo.size', + type: 'alias', + }, + 'suricata.eve.icmp_type': { + category: 'suricata', + name: 'suricata.eve.icmp_type', + type: 'long', + }, + 'suricata.eve.dest_port': { + category: 'suricata', + name: 'suricata.eve.dest_port', + type: 'alias', + }, + 'suricata.eve.src_port': { + category: 'suricata', + name: 'suricata.eve.src_port', + type: 'alias', + }, + 'suricata.eve.proto': { + category: 'suricata', + name: 'suricata.eve.proto', + type: 'alias', + }, + 'suricata.eve.pcap_cnt': { + category: 'suricata', + name: 'suricata.eve.pcap_cnt', + type: 'long', + }, + 'suricata.eve.src_ip': { + category: 'suricata', + name: 'suricata.eve.src_ip', + type: 'alias', + }, + 'suricata.eve.dns.type': { + category: 'suricata', + name: 'suricata.eve.dns.type', + type: 'keyword', + }, + 'suricata.eve.dns.rrtype': { + category: 'suricata', + name: 'suricata.eve.dns.rrtype', + type: 'keyword', + }, + 'suricata.eve.dns.rrname': { + category: 'suricata', + name: 'suricata.eve.dns.rrname', + type: 'keyword', + }, + 'suricata.eve.dns.rdata': { + category: 'suricata', + name: 'suricata.eve.dns.rdata', + type: 'keyword', + }, + 'suricata.eve.dns.tx_id': { + category: 'suricata', + name: 'suricata.eve.dns.tx_id', + type: 'long', + }, + 'suricata.eve.dns.ttl': { + category: 'suricata', + name: 'suricata.eve.dns.ttl', + type: 'long', + }, + 'suricata.eve.dns.rcode': { + category: 'suricata', + name: 'suricata.eve.dns.rcode', + type: 'keyword', + }, + 'suricata.eve.dns.id': { + category: 'suricata', + name: 'suricata.eve.dns.id', + type: 'long', + }, + 'suricata.eve.flow_id': { + category: 'suricata', + name: 'suricata.eve.flow_id', + type: 'keyword', + }, + 'suricata.eve.email.status': { + category: 'suricata', + name: 'suricata.eve.email.status', + type: 'keyword', + }, + 'suricata.eve.dest_ip': { + category: 'suricata', + name: 'suricata.eve.dest_ip', + type: 'alias', + }, + 'suricata.eve.icmp_code': { + category: 'suricata', + name: 'suricata.eve.icmp_code', + type: 'long', + }, + 'suricata.eve.http.status': { + category: 'suricata', + name: 'suricata.eve.http.status', + type: 'alias', + }, + 'suricata.eve.http.redirect': { + category: 'suricata', + name: 'suricata.eve.http.redirect', + type: 'keyword', + }, + 'suricata.eve.http.http_user_agent': { + category: 'suricata', + name: 'suricata.eve.http.http_user_agent', + type: 'alias', + }, + 'suricata.eve.http.protocol': { + category: 'suricata', + name: 'suricata.eve.http.protocol', + type: 'keyword', + }, + 'suricata.eve.http.http_refer': { + category: 'suricata', + name: 'suricata.eve.http.http_refer', + type: 'alias', + }, + 'suricata.eve.http.url': { + category: 'suricata', + name: 'suricata.eve.http.url', + type: 'alias', + }, + 'suricata.eve.http.hostname': { + category: 'suricata', + name: 'suricata.eve.http.hostname', + type: 'alias', + }, + 'suricata.eve.http.length': { + category: 'suricata', + name: 'suricata.eve.http.length', + type: 'alias', + }, + 'suricata.eve.http.http_method': { + category: 'suricata', + name: 'suricata.eve.http.http_method', + type: 'alias', + }, + 'suricata.eve.http.http_content_type': { + category: 'suricata', + name: 'suricata.eve.http.http_content_type', + type: 'keyword', + }, + 'suricata.eve.timestamp': { + category: 'suricata', + name: 'suricata.eve.timestamp', + type: 'alias', + }, + 'suricata.eve.in_iface': { + category: 'suricata', + name: 'suricata.eve.in_iface', + type: 'keyword', + }, + 'suricata.eve.alert.category': { + category: 'suricata', + name: 'suricata.eve.alert.category', + type: 'keyword', + }, + 'suricata.eve.alert.severity': { + category: 'suricata', + name: 'suricata.eve.alert.severity', + type: 'alias', + }, + 'suricata.eve.alert.rev': { + category: 'suricata', + name: 'suricata.eve.alert.rev', + type: 'long', + }, + 'suricata.eve.alert.gid': { + category: 'suricata', + name: 'suricata.eve.alert.gid', + type: 'long', + }, + 'suricata.eve.alert.signature': { + category: 'suricata', + name: 'suricata.eve.alert.signature', + type: 'keyword', + }, + 'suricata.eve.alert.action': { + category: 'suricata', + name: 'suricata.eve.alert.action', + type: 'alias', + }, + 'suricata.eve.alert.signature_id': { + category: 'suricata', + name: 'suricata.eve.alert.signature_id', + type: 'long', + }, + 'suricata.eve.ssh.client.proto_version': { + category: 'suricata', + name: 'suricata.eve.ssh.client.proto_version', + type: 'keyword', + }, + 'suricata.eve.ssh.client.software_version': { + category: 'suricata', + name: 'suricata.eve.ssh.client.software_version', + type: 'keyword', + }, + 'suricata.eve.ssh.server.proto_version': { + category: 'suricata', + name: 'suricata.eve.ssh.server.proto_version', + type: 'keyword', + }, + 'suricata.eve.ssh.server.software_version': { + category: 'suricata', + name: 'suricata.eve.ssh.server.software_version', + type: 'keyword', + }, + 'suricata.eve.stats.capture.kernel_packets': { + category: 'suricata', + name: 'suricata.eve.stats.capture.kernel_packets', + type: 'long', + }, + 'suricata.eve.stats.capture.kernel_drops': { + category: 'suricata', + name: 'suricata.eve.stats.capture.kernel_drops', + type: 'long', + }, + 'suricata.eve.stats.capture.kernel_ifdrops': { + category: 'suricata', + name: 'suricata.eve.stats.capture.kernel_ifdrops', + type: 'long', + }, + 'suricata.eve.stats.uptime': { + category: 'suricata', + name: 'suricata.eve.stats.uptime', + type: 'long', + }, + 'suricata.eve.stats.detect.alert': { + category: 'suricata', + name: 'suricata.eve.stats.detect.alert', + type: 'long', + }, + 'suricata.eve.stats.http.memcap': { + category: 'suricata', + name: 'suricata.eve.stats.http.memcap', + type: 'long', + }, + 'suricata.eve.stats.http.memuse': { + category: 'suricata', + name: 'suricata.eve.stats.http.memuse', + type: 'long', + }, + 'suricata.eve.stats.file_store.open_files': { + category: 'suricata', + name: 'suricata.eve.stats.file_store.open_files', + type: 'long', + }, + 'suricata.eve.stats.defrag.max_frag_hits': { + category: 'suricata', + name: 'suricata.eve.stats.defrag.max_frag_hits', + type: 'long', + }, + 'suricata.eve.stats.defrag.ipv4.timeouts': { + category: 'suricata', + name: 'suricata.eve.stats.defrag.ipv4.timeouts', + type: 'long', + }, + 'suricata.eve.stats.defrag.ipv4.fragments': { + category: 'suricata', + name: 'suricata.eve.stats.defrag.ipv4.fragments', + type: 'long', + }, + 'suricata.eve.stats.defrag.ipv4.reassembled': { + category: 'suricata', + name: 'suricata.eve.stats.defrag.ipv4.reassembled', + type: 'long', + }, + 'suricata.eve.stats.defrag.ipv6.timeouts': { + category: 'suricata', + name: 'suricata.eve.stats.defrag.ipv6.timeouts', + type: 'long', + }, + 'suricata.eve.stats.defrag.ipv6.fragments': { + category: 'suricata', + name: 'suricata.eve.stats.defrag.ipv6.fragments', + type: 'long', + }, + 'suricata.eve.stats.defrag.ipv6.reassembled': { + category: 'suricata', + name: 'suricata.eve.stats.defrag.ipv6.reassembled', + type: 'long', + }, + 'suricata.eve.stats.flow.tcp_reuse': { + category: 'suricata', + name: 'suricata.eve.stats.flow.tcp_reuse', + type: 'long', + }, + 'suricata.eve.stats.flow.udp': { + category: 'suricata', + name: 'suricata.eve.stats.flow.udp', + type: 'long', + }, + 'suricata.eve.stats.flow.memcap': { + category: 'suricata', + name: 'suricata.eve.stats.flow.memcap', + type: 'long', + }, + 'suricata.eve.stats.flow.emerg_mode_entered': { + category: 'suricata', + name: 'suricata.eve.stats.flow.emerg_mode_entered', + type: 'long', + }, + 'suricata.eve.stats.flow.emerg_mode_over': { + category: 'suricata', + name: 'suricata.eve.stats.flow.emerg_mode_over', + type: 'long', + }, + 'suricata.eve.stats.flow.tcp': { + category: 'suricata', + name: 'suricata.eve.stats.flow.tcp', + type: 'long', + }, + 'suricata.eve.stats.flow.icmpv6': { + category: 'suricata', + name: 'suricata.eve.stats.flow.icmpv6', + type: 'long', + }, + 'suricata.eve.stats.flow.icmpv4': { + category: 'suricata', + name: 'suricata.eve.stats.flow.icmpv4', + type: 'long', + }, + 'suricata.eve.stats.flow.spare': { + category: 'suricata', + name: 'suricata.eve.stats.flow.spare', + type: 'long', + }, + 'suricata.eve.stats.flow.memuse': { + category: 'suricata', + name: 'suricata.eve.stats.flow.memuse', + type: 'long', + }, + 'suricata.eve.stats.tcp.pseudo_failed': { + category: 'suricata', + name: 'suricata.eve.stats.tcp.pseudo_failed', + type: 'long', + }, + 'suricata.eve.stats.tcp.ssn_memcap_drop': { + category: 'suricata', + name: 'suricata.eve.stats.tcp.ssn_memcap_drop', + type: 'long', + }, + 'suricata.eve.stats.tcp.insert_data_overlap_fail': { + category: 'suricata', + name: 'suricata.eve.stats.tcp.insert_data_overlap_fail', + type: 'long', + }, + 'suricata.eve.stats.tcp.sessions': { + category: 'suricata', + name: 'suricata.eve.stats.tcp.sessions', + type: 'long', + }, + 'suricata.eve.stats.tcp.pseudo': { + category: 'suricata', + name: 'suricata.eve.stats.tcp.pseudo', + type: 'long', + }, + 'suricata.eve.stats.tcp.synack': { + category: 'suricata', + name: 'suricata.eve.stats.tcp.synack', + type: 'long', + }, + 'suricata.eve.stats.tcp.insert_data_normal_fail': { + category: 'suricata', + name: 'suricata.eve.stats.tcp.insert_data_normal_fail', + type: 'long', + }, + 'suricata.eve.stats.tcp.syn': { + category: 'suricata', + name: 'suricata.eve.stats.tcp.syn', + type: 'long', + }, + 'suricata.eve.stats.tcp.memuse': { + category: 'suricata', + name: 'suricata.eve.stats.tcp.memuse', + type: 'long', + }, + 'suricata.eve.stats.tcp.invalid_checksum': { + category: 'suricata', + name: 'suricata.eve.stats.tcp.invalid_checksum', + type: 'long', + }, + 'suricata.eve.stats.tcp.segment_memcap_drop': { + category: 'suricata', + name: 'suricata.eve.stats.tcp.segment_memcap_drop', + type: 'long', + }, + 'suricata.eve.stats.tcp.overlap': { + category: 'suricata', + name: 'suricata.eve.stats.tcp.overlap', + type: 'long', + }, + 'suricata.eve.stats.tcp.insert_list_fail': { + category: 'suricata', + name: 'suricata.eve.stats.tcp.insert_list_fail', + type: 'long', + }, + 'suricata.eve.stats.tcp.rst': { + category: 'suricata', + name: 'suricata.eve.stats.tcp.rst', + type: 'long', + }, + 'suricata.eve.stats.tcp.stream_depth_reached': { + category: 'suricata', + name: 'suricata.eve.stats.tcp.stream_depth_reached', + type: 'long', + }, + 'suricata.eve.stats.tcp.reassembly_memuse': { + category: 'suricata', + name: 'suricata.eve.stats.tcp.reassembly_memuse', + type: 'long', + }, + 'suricata.eve.stats.tcp.reassembly_gap': { + category: 'suricata', + name: 'suricata.eve.stats.tcp.reassembly_gap', + type: 'long', + }, + 'suricata.eve.stats.tcp.overlap_diff_data': { + category: 'suricata', + name: 'suricata.eve.stats.tcp.overlap_diff_data', + type: 'long', + }, + 'suricata.eve.stats.tcp.no_flow': { + category: 'suricata', + name: 'suricata.eve.stats.tcp.no_flow', + type: 'long', + }, + 'suricata.eve.stats.decoder.avg_pkt_size': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.avg_pkt_size', + type: 'long', + }, + 'suricata.eve.stats.decoder.bytes': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.bytes', + type: 'long', + }, + 'suricata.eve.stats.decoder.tcp': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.tcp', + type: 'long', + }, + 'suricata.eve.stats.decoder.raw': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.raw', + type: 'long', + }, + 'suricata.eve.stats.decoder.ppp': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.ppp', + type: 'long', + }, + 'suricata.eve.stats.decoder.vlan_qinq': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.vlan_qinq', + type: 'long', + }, + 'suricata.eve.stats.decoder.null': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.null', + type: 'long', + }, + 'suricata.eve.stats.decoder.ltnull.unsupported_type': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.ltnull.unsupported_type', + type: 'long', + }, + 'suricata.eve.stats.decoder.ltnull.pkt_too_small': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.ltnull.pkt_too_small', + type: 'long', + }, + 'suricata.eve.stats.decoder.invalid': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.invalid', + type: 'long', + }, + 'suricata.eve.stats.decoder.gre': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.gre', + type: 'long', + }, + 'suricata.eve.stats.decoder.ipv4': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.ipv4', + type: 'long', + }, + 'suricata.eve.stats.decoder.ipv6': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.ipv6', + type: 'long', + }, + 'suricata.eve.stats.decoder.pkts': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.pkts', + type: 'long', + }, + 'suricata.eve.stats.decoder.ipv6_in_ipv6': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.ipv6_in_ipv6', + type: 'long', + }, + 'suricata.eve.stats.decoder.ipraw.invalid_ip_version': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.ipraw.invalid_ip_version', + type: 'long', + }, + 'suricata.eve.stats.decoder.pppoe': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.pppoe', + type: 'long', + }, + 'suricata.eve.stats.decoder.udp': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.udp', + type: 'long', + }, + 'suricata.eve.stats.decoder.dce.pkt_too_small': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.dce.pkt_too_small', + type: 'long', + }, + 'suricata.eve.stats.decoder.vlan': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.vlan', + type: 'long', + }, + 'suricata.eve.stats.decoder.sctp': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.sctp', + type: 'long', + }, + 'suricata.eve.stats.decoder.max_pkt_size': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.max_pkt_size', + type: 'long', + }, + 'suricata.eve.stats.decoder.teredo': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.teredo', + type: 'long', + }, + 'suricata.eve.stats.decoder.mpls': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.mpls', + type: 'long', + }, + 'suricata.eve.stats.decoder.sll': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.sll', + type: 'long', + }, + 'suricata.eve.stats.decoder.icmpv6': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.icmpv6', + type: 'long', + }, + 'suricata.eve.stats.decoder.icmpv4': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.icmpv4', + type: 'long', + }, + 'suricata.eve.stats.decoder.erspan': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.erspan', + type: 'long', + }, + 'suricata.eve.stats.decoder.ethernet': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.ethernet', + type: 'long', + }, + 'suricata.eve.stats.decoder.ipv4_in_ipv6': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.ipv4_in_ipv6', + type: 'long', + }, + 'suricata.eve.stats.decoder.ieee8021ah': { + category: 'suricata', + name: 'suricata.eve.stats.decoder.ieee8021ah', + type: 'long', + }, + 'suricata.eve.stats.dns.memcap_global': { + category: 'suricata', + name: 'suricata.eve.stats.dns.memcap_global', + type: 'long', + }, + 'suricata.eve.stats.dns.memcap_state': { + category: 'suricata', + name: 'suricata.eve.stats.dns.memcap_state', + type: 'long', + }, + 'suricata.eve.stats.dns.memuse': { + category: 'suricata', + name: 'suricata.eve.stats.dns.memuse', + type: 'long', + }, + 'suricata.eve.stats.flow_mgr.rows_busy': { + category: 'suricata', + name: 'suricata.eve.stats.flow_mgr.rows_busy', + type: 'long', + }, + 'suricata.eve.stats.flow_mgr.flows_timeout': { + category: 'suricata', + name: 'suricata.eve.stats.flow_mgr.flows_timeout', + type: 'long', + }, + 'suricata.eve.stats.flow_mgr.flows_notimeout': { + category: 'suricata', + name: 'suricata.eve.stats.flow_mgr.flows_notimeout', + type: 'long', + }, + 'suricata.eve.stats.flow_mgr.rows_skipped': { + category: 'suricata', + name: 'suricata.eve.stats.flow_mgr.rows_skipped', + type: 'long', + }, + 'suricata.eve.stats.flow_mgr.closed_pruned': { + category: 'suricata', + name: 'suricata.eve.stats.flow_mgr.closed_pruned', + type: 'long', + }, + 'suricata.eve.stats.flow_mgr.new_pruned': { + category: 'suricata', + name: 'suricata.eve.stats.flow_mgr.new_pruned', + type: 'long', + }, + 'suricata.eve.stats.flow_mgr.flows_removed': { + category: 'suricata', + name: 'suricata.eve.stats.flow_mgr.flows_removed', + type: 'long', + }, + 'suricata.eve.stats.flow_mgr.bypassed_pruned': { + category: 'suricata', + name: 'suricata.eve.stats.flow_mgr.bypassed_pruned', + type: 'long', + }, + 'suricata.eve.stats.flow_mgr.est_pruned': { + category: 'suricata', + name: 'suricata.eve.stats.flow_mgr.est_pruned', + type: 'long', + }, + 'suricata.eve.stats.flow_mgr.flows_timeout_inuse': { + category: 'suricata', + name: 'suricata.eve.stats.flow_mgr.flows_timeout_inuse', + type: 'long', + }, + 'suricata.eve.stats.flow_mgr.flows_checked': { + category: 'suricata', + name: 'suricata.eve.stats.flow_mgr.flows_checked', + type: 'long', + }, + 'suricata.eve.stats.flow_mgr.rows_maxlen': { + category: 'suricata', + name: 'suricata.eve.stats.flow_mgr.rows_maxlen', + type: 'long', + }, + 'suricata.eve.stats.flow_mgr.rows_checked': { + category: 'suricata', + name: 'suricata.eve.stats.flow_mgr.rows_checked', + type: 'long', + }, + 'suricata.eve.stats.flow_mgr.rows_empty': { + category: 'suricata', + name: 'suricata.eve.stats.flow_mgr.rows_empty', + type: 'long', + }, + 'suricata.eve.stats.app_layer.flow.tls': { + category: 'suricata', + name: 'suricata.eve.stats.app_layer.flow.tls', + type: 'long', + }, + 'suricata.eve.stats.app_layer.flow.ftp': { + category: 'suricata', + name: 'suricata.eve.stats.app_layer.flow.ftp', + type: 'long', + }, + 'suricata.eve.stats.app_layer.flow.http': { + category: 'suricata', + name: 'suricata.eve.stats.app_layer.flow.http', + type: 'long', + }, + 'suricata.eve.stats.app_layer.flow.failed_udp': { + category: 'suricata', + name: 'suricata.eve.stats.app_layer.flow.failed_udp', + type: 'long', + }, + 'suricata.eve.stats.app_layer.flow.dns_udp': { + category: 'suricata', + name: 'suricata.eve.stats.app_layer.flow.dns_udp', + type: 'long', + }, + 'suricata.eve.stats.app_layer.flow.dns_tcp': { + category: 'suricata', + name: 'suricata.eve.stats.app_layer.flow.dns_tcp', + type: 'long', + }, + 'suricata.eve.stats.app_layer.flow.smtp': { + category: 'suricata', + name: 'suricata.eve.stats.app_layer.flow.smtp', + type: 'long', + }, + 'suricata.eve.stats.app_layer.flow.failed_tcp': { + category: 'suricata', + name: 'suricata.eve.stats.app_layer.flow.failed_tcp', + type: 'long', + }, + 'suricata.eve.stats.app_layer.flow.msn': { + category: 'suricata', + name: 'suricata.eve.stats.app_layer.flow.msn', + type: 'long', + }, + 'suricata.eve.stats.app_layer.flow.ssh': { + category: 'suricata', + name: 'suricata.eve.stats.app_layer.flow.ssh', + type: 'long', + }, + 'suricata.eve.stats.app_layer.flow.imap': { + category: 'suricata', + name: 'suricata.eve.stats.app_layer.flow.imap', + type: 'long', + }, + 'suricata.eve.stats.app_layer.flow.dcerpc_udp': { + category: 'suricata', + name: 'suricata.eve.stats.app_layer.flow.dcerpc_udp', + type: 'long', + }, + 'suricata.eve.stats.app_layer.flow.dcerpc_tcp': { + category: 'suricata', + name: 'suricata.eve.stats.app_layer.flow.dcerpc_tcp', + type: 'long', + }, + 'suricata.eve.stats.app_layer.flow.smb': { + category: 'suricata', + name: 'suricata.eve.stats.app_layer.flow.smb', + type: 'long', + }, + 'suricata.eve.stats.app_layer.tx.tls': { + category: 'suricata', + name: 'suricata.eve.stats.app_layer.tx.tls', + type: 'long', + }, + 'suricata.eve.stats.app_layer.tx.ftp': { + category: 'suricata', + name: 'suricata.eve.stats.app_layer.tx.ftp', + type: 'long', + }, + 'suricata.eve.stats.app_layer.tx.http': { + category: 'suricata', + name: 'suricata.eve.stats.app_layer.tx.http', + type: 'long', + }, + 'suricata.eve.stats.app_layer.tx.dns_udp': { + category: 'suricata', + name: 'suricata.eve.stats.app_layer.tx.dns_udp', + type: 'long', + }, + 'suricata.eve.stats.app_layer.tx.dns_tcp': { + category: 'suricata', + name: 'suricata.eve.stats.app_layer.tx.dns_tcp', + type: 'long', + }, + 'suricata.eve.stats.app_layer.tx.smtp': { + category: 'suricata', + name: 'suricata.eve.stats.app_layer.tx.smtp', + type: 'long', + }, + 'suricata.eve.stats.app_layer.tx.ssh': { + category: 'suricata', + name: 'suricata.eve.stats.app_layer.tx.ssh', + type: 'long', + }, + 'suricata.eve.stats.app_layer.tx.dcerpc_udp': { + category: 'suricata', + name: 'suricata.eve.stats.app_layer.tx.dcerpc_udp', + type: 'long', + }, + 'suricata.eve.stats.app_layer.tx.dcerpc_tcp': { + category: 'suricata', + name: 'suricata.eve.stats.app_layer.tx.dcerpc_tcp', + type: 'long', + }, + 'suricata.eve.stats.app_layer.tx.smb': { + category: 'suricata', + name: 'suricata.eve.stats.app_layer.tx.smb', + type: 'long', + }, + 'suricata.eve.tls.notbefore': { + category: 'suricata', + name: 'suricata.eve.tls.notbefore', + type: 'date', + }, + 'suricata.eve.tls.issuerdn': { + category: 'suricata', + name: 'suricata.eve.tls.issuerdn', + type: 'keyword', + }, + 'suricata.eve.tls.sni': { + category: 'suricata', + name: 'suricata.eve.tls.sni', + type: 'keyword', + }, + 'suricata.eve.tls.version': { + category: 'suricata', + name: 'suricata.eve.tls.version', + type: 'keyword', + }, + 'suricata.eve.tls.session_resumed': { + category: 'suricata', + name: 'suricata.eve.tls.session_resumed', + type: 'boolean', + }, + 'suricata.eve.tls.fingerprint': { + category: 'suricata', + name: 'suricata.eve.tls.fingerprint', + type: 'keyword', + }, + 'suricata.eve.tls.serial': { + category: 'suricata', + name: 'suricata.eve.tls.serial', + type: 'keyword', + }, + 'suricata.eve.tls.notafter': { + category: 'suricata', + name: 'suricata.eve.tls.notafter', + type: 'date', + }, + 'suricata.eve.tls.subject': { + category: 'suricata', + name: 'suricata.eve.tls.subject', + type: 'keyword', + }, + 'suricata.eve.tls.ja3s.string': { + category: 'suricata', + name: 'suricata.eve.tls.ja3s.string', + type: 'keyword', + }, + 'suricata.eve.tls.ja3s.hash': { + category: 'suricata', + name: 'suricata.eve.tls.ja3s.hash', + type: 'keyword', + }, + 'suricata.eve.tls.ja3.string': { + category: 'suricata', + name: 'suricata.eve.tls.ja3.string', + type: 'keyword', + }, + 'suricata.eve.tls.ja3.hash': { + category: 'suricata', + name: 'suricata.eve.tls.ja3.hash', + type: 'keyword', + }, + 'suricata.eve.app_proto_ts': { + category: 'suricata', + name: 'suricata.eve.app_proto_ts', + type: 'keyword', + }, + 'suricata.eve.flow.bytes_toclient': { + category: 'suricata', + name: 'suricata.eve.flow.bytes_toclient', + type: 'alias', + }, + 'suricata.eve.flow.start': { + category: 'suricata', + name: 'suricata.eve.flow.start', + type: 'alias', + }, + 'suricata.eve.flow.pkts_toclient': { + category: 'suricata', + name: 'suricata.eve.flow.pkts_toclient', + type: 'alias', + }, + 'suricata.eve.flow.age': { + category: 'suricata', + name: 'suricata.eve.flow.age', + type: 'long', + }, + 'suricata.eve.flow.state': { + category: 'suricata', + name: 'suricata.eve.flow.state', + type: 'keyword', + }, + 'suricata.eve.flow.bytes_toserver': { + category: 'suricata', + name: 'suricata.eve.flow.bytes_toserver', + type: 'alias', + }, + 'suricata.eve.flow.reason': { + category: 'suricata', + name: 'suricata.eve.flow.reason', + type: 'keyword', + }, + 'suricata.eve.flow.pkts_toserver': { + category: 'suricata', + name: 'suricata.eve.flow.pkts_toserver', + type: 'alias', + }, + 'suricata.eve.flow.end': { + category: 'suricata', + name: 'suricata.eve.flow.end', + type: 'date', + }, + 'suricata.eve.flow.alerted': { + category: 'suricata', + name: 'suricata.eve.flow.alerted', + type: 'boolean', + }, + 'suricata.eve.app_proto': { + category: 'suricata', + name: 'suricata.eve.app_proto', + type: 'alias', + }, + 'suricata.eve.tx_id': { + category: 'suricata', + name: 'suricata.eve.tx_id', + type: 'long', + }, + 'suricata.eve.app_proto_tc': { + category: 'suricata', + name: 'suricata.eve.app_proto_tc', + type: 'keyword', + }, + 'suricata.eve.smtp.rcpt_to': { + category: 'suricata', + name: 'suricata.eve.smtp.rcpt_to', + type: 'keyword', + }, + 'suricata.eve.smtp.mail_from': { + category: 'suricata', + name: 'suricata.eve.smtp.mail_from', + type: 'keyword', + }, + 'suricata.eve.smtp.helo': { + category: 'suricata', + name: 'suricata.eve.smtp.helo', + type: 'keyword', + }, + 'suricata.eve.app_proto_expected': { + category: 'suricata', + name: 'suricata.eve.app_proto_expected', + type: 'keyword', + }, + 'suricata.eve.flags': { + category: 'suricata', + name: 'suricata.eve.flags', + type: 'group', + }, + 'zeek.session_id': { + category: 'zeek', + description: 'A unique identifier of the session ', + name: 'zeek.session_id', + type: 'keyword', + }, + 'zeek.capture_loss.ts_delta': { + category: 'zeek', + description: 'The time delay between this measurement and the last. ', + name: 'zeek.capture_loss.ts_delta', + type: 'integer', + }, + 'zeek.capture_loss.peer': { + category: 'zeek', + description: + 'In the event that there are multiple Bro instances logging to the same host, this distinguishes each peer with its individual name. ', + name: 'zeek.capture_loss.peer', + type: 'keyword', + }, + 'zeek.capture_loss.gaps': { + category: 'zeek', + description: 'Number of missed ACKs from the previous measurement interval. ', + name: 'zeek.capture_loss.gaps', + type: 'integer', + }, + 'zeek.capture_loss.acks': { + category: 'zeek', + description: 'Total number of ACKs seen in the previous measurement interval. ', + name: 'zeek.capture_loss.acks', + type: 'integer', + }, + 'zeek.capture_loss.percent_lost': { + category: 'zeek', + description: "Percentage of ACKs seen where the data being ACKed wasn't seen. ", + name: 'zeek.capture_loss.percent_lost', + type: 'double', + }, + 'zeek.connection.local_orig': { + category: 'zeek', + description: 'Indicates whether the session is originated locally. ', + name: 'zeek.connection.local_orig', + type: 'boolean', + }, + 'zeek.connection.local_resp': { + category: 'zeek', + description: 'Indicates whether the session is responded locally. ', + name: 'zeek.connection.local_resp', + type: 'boolean', + }, + 'zeek.connection.missed_bytes': { + category: 'zeek', + description: 'Missed bytes for the session. ', + name: 'zeek.connection.missed_bytes', + type: 'long', + }, + 'zeek.connection.state': { + category: 'zeek', + description: 'Code indicating the state of the session. ', + name: 'zeek.connection.state', + type: 'keyword', + }, + 'zeek.connection.state_message': { + category: 'zeek', + description: 'The state of the session. ', + name: 'zeek.connection.state_message', + type: 'keyword', + }, + 'zeek.connection.icmp.type': { + category: 'zeek', + description: 'ICMP message type. ', + name: 'zeek.connection.icmp.type', + type: 'integer', + }, + 'zeek.connection.icmp.code': { + category: 'zeek', + description: 'ICMP message code. ', + name: 'zeek.connection.icmp.code', + type: 'integer', + }, + 'zeek.connection.history': { + category: 'zeek', + description: 'Flags indicating the history of the session. ', + name: 'zeek.connection.history', + type: 'keyword', + }, + 'zeek.connection.vlan': { + category: 'zeek', + description: 'VLAN identifier. ', + name: 'zeek.connection.vlan', + type: 'integer', + }, + 'zeek.connection.inner_vlan': { + category: 'zeek', + description: 'VLAN identifier. ', + name: 'zeek.connection.inner_vlan', + type: 'integer', + }, + 'zeek.dce_rpc.rtt': { + category: 'zeek', + description: + "Round trip time from the request to the response. If either the request or response wasn't seen, this will be null. ", + name: 'zeek.dce_rpc.rtt', + type: 'integer', + }, + 'zeek.dce_rpc.named_pipe': { + category: 'zeek', + description: 'Remote pipe name. ', + name: 'zeek.dce_rpc.named_pipe', + type: 'keyword', + }, + 'zeek.dce_rpc.endpoint': { + category: 'zeek', + description: 'Endpoint name looked up from the uuid. ', + name: 'zeek.dce_rpc.endpoint', + type: 'keyword', + }, + 'zeek.dce_rpc.operation': { + category: 'zeek', + description: 'Operation seen in the call. ', + name: 'zeek.dce_rpc.operation', + type: 'keyword', + }, + 'zeek.dhcp.domain': { + category: 'zeek', + description: 'Domain given by the server in option 15. ', + name: 'zeek.dhcp.domain', + type: 'keyword', + }, + 'zeek.dhcp.duration': { + category: 'zeek', + description: + 'Duration of the DHCP session representing the time from the first message to the last, in seconds. ', + name: 'zeek.dhcp.duration', + type: 'double', + }, + 'zeek.dhcp.hostname': { + category: 'zeek', + description: 'Name given by client in Hostname option 12. ', + name: 'zeek.dhcp.hostname', + type: 'keyword', + }, + 'zeek.dhcp.client_fqdn': { + category: 'zeek', + description: 'FQDN given by client in Client FQDN option 81. ', + name: 'zeek.dhcp.client_fqdn', + type: 'keyword', + }, + 'zeek.dhcp.lease_time': { + category: 'zeek', + description: 'IP address lease interval in seconds. ', + name: 'zeek.dhcp.lease_time', + type: 'integer', + }, + 'zeek.dhcp.address.assigned': { + category: 'zeek', + description: 'IP address assigned by the server. ', + name: 'zeek.dhcp.address.assigned', + type: 'ip', + }, + 'zeek.dhcp.address.client': { + category: 'zeek', + description: + 'IP address of the client. If a transaction is only a client sending INFORM messages then there is no lease information exchanged so this is helpful to know who sent the messages. Getting an address in this field does require that the client sources at least one DHCP message using a non-broadcast address. ', + name: 'zeek.dhcp.address.client', + type: 'ip', + }, + 'zeek.dhcp.address.mac': { + category: 'zeek', + description: "Client's hardware address. ", + name: 'zeek.dhcp.address.mac', + type: 'keyword', + }, + 'zeek.dhcp.address.requested': { + category: 'zeek', + description: 'IP address requested by the client. ', + name: 'zeek.dhcp.address.requested', + type: 'ip', + }, + 'zeek.dhcp.address.server': { + category: 'zeek', + description: 'IP address of the DHCP server. ', + name: 'zeek.dhcp.address.server', + type: 'ip', + }, + 'zeek.dhcp.msg.types': { + category: 'zeek', + description: 'List of DHCP message types seen in this exchange. ', + name: 'zeek.dhcp.msg.types', + type: 'keyword', + }, + 'zeek.dhcp.msg.origin': { + category: 'zeek', + description: + '(present if policy/protocols/dhcp/msg-orig.bro is loaded) The address that originated each message from the msg.types field. ', + name: 'zeek.dhcp.msg.origin', + type: 'ip', + }, + 'zeek.dhcp.msg.client': { + category: 'zeek', + description: + 'Message typically accompanied with a DHCP_DECLINE so the client can tell the server why it rejected an address. ', + name: 'zeek.dhcp.msg.client', + type: 'keyword', + }, + 'zeek.dhcp.msg.server': { + category: 'zeek', + description: + 'Message typically accompanied with a DHCP_NAK to let the client know why it rejected the request. ', + name: 'zeek.dhcp.msg.server', + type: 'keyword', + }, + 'zeek.dhcp.software.client': { + category: 'zeek', + description: + '(present if policy/protocols/dhcp/software.bro is loaded) Software reported by the client in the vendor_class option. ', + name: 'zeek.dhcp.software.client', + type: 'keyword', + }, + 'zeek.dhcp.software.server': { + category: 'zeek', + description: + '(present if policy/protocols/dhcp/software.bro is loaded) Software reported by the client in the vendor_class option. ', + name: 'zeek.dhcp.software.server', + type: 'keyword', + }, + 'zeek.dhcp.id.circuit': { + category: 'zeek', + description: + '(present if policy/protocols/dhcp/sub-opts.bro is loaded) Added by DHCP relay agents which terminate switched or permanent circuits. It encodes an agent-local identifier of the circuit from which a DHCP client-to-server packet was received. Typically it should represent a router or switch interface number. ', + name: 'zeek.dhcp.id.circuit', + type: 'keyword', + }, + 'zeek.dhcp.id.remote_agent': { + category: 'zeek', + description: + '(present if policy/protocols/dhcp/sub-opts.bro is loaded) A globally unique identifier added by relay agents to identify the remote host end of the circuit. ', + name: 'zeek.dhcp.id.remote_agent', + type: 'keyword', + }, + 'zeek.dhcp.id.subscriber': { + category: 'zeek', + description: + "(present if policy/protocols/dhcp/sub-opts.bro is loaded) The subscriber ID is a value independent of the physical network configuration so that a customer's DHCP configuration can be given to them correctly no matter where they are physically connected. ", + name: 'zeek.dhcp.id.subscriber', + type: 'keyword', + }, + 'zeek.dnp3.function.request': { + category: 'zeek', + description: 'The name of the function message in the request. ', + name: 'zeek.dnp3.function.request', + type: 'keyword', + }, + 'zeek.dnp3.function.reply': { + category: 'zeek', + description: 'The name of the function message in the reply. ', + name: 'zeek.dnp3.function.reply', + type: 'keyword', + }, + 'zeek.dnp3.id': { + category: 'zeek', + description: "The response's internal indication number. ", + name: 'zeek.dnp3.id', + type: 'integer', + }, + 'zeek.dns.trans_id': { + category: 'zeek', + description: 'DNS transaction identifier. ', + name: 'zeek.dns.trans_id', + type: 'keyword', + }, + 'zeek.dns.rtt': { + category: 'zeek', + description: 'Round trip time for the query and response. ', + name: 'zeek.dns.rtt', + type: 'double', + }, + 'zeek.dns.query': { + category: 'zeek', + description: 'The domain name that is the subject of the DNS query. ', + name: 'zeek.dns.query', + type: 'keyword', + }, + 'zeek.dns.qclass': { + category: 'zeek', + description: 'The QCLASS value specifying the class of the query. ', + name: 'zeek.dns.qclass', + type: 'long', + }, + 'zeek.dns.qclass_name': { + category: 'zeek', + description: 'A descriptive name for the class of the query. ', + name: 'zeek.dns.qclass_name', + type: 'keyword', + }, + 'zeek.dns.qtype': { + category: 'zeek', + description: 'A QTYPE value specifying the type of the query. ', + name: 'zeek.dns.qtype', + type: 'long', + }, + 'zeek.dns.qtype_name': { + category: 'zeek', + description: 'A descriptive name for the type of the query. ', + name: 'zeek.dns.qtype_name', + type: 'keyword', + }, + 'zeek.dns.rcode': { + category: 'zeek', + description: 'The response code value in DNS response messages. ', + name: 'zeek.dns.rcode', + type: 'long', + }, + 'zeek.dns.rcode_name': { + category: 'zeek', + description: 'A descriptive name for the response code value. ', + name: 'zeek.dns.rcode_name', + type: 'keyword', + }, + 'zeek.dns.AA': { + category: 'zeek', + description: + 'The Authoritative Answer bit for response messages specifies that the responding name server is an authority for the domain name in the question section. ', + name: 'zeek.dns.AA', + type: 'boolean', + }, + 'zeek.dns.TC': { + category: 'zeek', + description: 'The Truncation bit specifies that the message was truncated. ', + name: 'zeek.dns.TC', + type: 'boolean', + }, + 'zeek.dns.RD': { + category: 'zeek', + description: + 'The Recursion Desired bit in a request message indicates that the client wants recursive service for this query. ', + name: 'zeek.dns.RD', + type: 'boolean', + }, + 'zeek.dns.RA': { + category: 'zeek', + description: + 'The Recursion Available bit in a response message indicates that the name server supports recursive queries. ', + name: 'zeek.dns.RA', + type: 'boolean', + }, + 'zeek.dns.answers': { + category: 'zeek', + description: 'The set of resource descriptions in the query answer. ', + name: 'zeek.dns.answers', + type: 'keyword', + }, + 'zeek.dns.TTLs': { + category: 'zeek', + description: 'The caching intervals of the associated RRs described by the answers field. ', + name: 'zeek.dns.TTLs', + type: 'double', + }, + 'zeek.dns.rejected': { + category: 'zeek', + description: 'Indicates whether the DNS query was rejected by the server. ', + name: 'zeek.dns.rejected', + type: 'boolean', + }, + 'zeek.dns.total_answers': { + category: 'zeek', + description: 'The total number of resource records in the reply. ', + name: 'zeek.dns.total_answers', + type: 'integer', + }, + 'zeek.dns.total_replies': { + category: 'zeek', + description: 'The total number of resource records in the reply message. ', + name: 'zeek.dns.total_replies', + type: 'integer', + }, + 'zeek.dns.saw_query': { + category: 'zeek', + description: 'Whether the full DNS query has been seen. ', + name: 'zeek.dns.saw_query', + type: 'boolean', + }, + 'zeek.dns.saw_reply': { + category: 'zeek', + description: 'Whether the full DNS reply has been seen. ', + name: 'zeek.dns.saw_reply', + type: 'boolean', + }, + 'zeek.dpd.analyzer': { + category: 'zeek', + description: 'The analyzer that generated the violation. ', + name: 'zeek.dpd.analyzer', + type: 'keyword', + }, + 'zeek.dpd.failure_reason': { + category: 'zeek', + description: 'The textual reason for the analysis failure. ', + name: 'zeek.dpd.failure_reason', + type: 'keyword', + }, + 'zeek.dpd.packet_segment': { + category: 'zeek', + description: + '(present if policy/frameworks/dpd/packet-segment-logging.bro is loaded) A chunk of the payload that most likely resulted in the protocol violation. ', + name: 'zeek.dpd.packet_segment', + type: 'keyword', + }, + 'zeek.files.fuid': { + category: 'zeek', + description: 'A file unique identifier. ', + name: 'zeek.files.fuid', + type: 'keyword', + }, + 'zeek.files.tx_host': { + category: 'zeek', + description: 'The host that transferred the file. ', + name: 'zeek.files.tx_host', + type: 'ip', + }, + 'zeek.files.rx_host': { + category: 'zeek', + description: 'The host that received the file. ', + name: 'zeek.files.rx_host', + type: 'ip', + }, + 'zeek.files.session_ids': { + category: 'zeek', + description: 'The sessions that have this file. ', + name: 'zeek.files.session_ids', + type: 'keyword', + }, + 'zeek.files.source': { + category: 'zeek', + description: + 'An identification of the source of the file data. E.g. it may be a network protocol over which it was transferred, or a local file path which was read, or some other input source. ', + name: 'zeek.files.source', + type: 'keyword', + }, + 'zeek.files.depth': { + category: 'zeek', + description: + 'A value to represent the depth of this file in relation to its source. In SMTP, it is the depth of the MIME attachment on the message. In HTTP, it is the depth of the request within the TCP connection. ', + name: 'zeek.files.depth', + type: 'long', + }, + 'zeek.files.analyzers': { + category: 'zeek', + description: 'A set of analysis types done during the file analysis. ', + name: 'zeek.files.analyzers', + type: 'keyword', + }, + 'zeek.files.mime_type': { + category: 'zeek', + description: 'Mime type of the file. ', + name: 'zeek.files.mime_type', + type: 'keyword', + }, + 'zeek.files.filename': { + category: 'zeek', + description: 'Name of the file if available. ', + name: 'zeek.files.filename', + type: 'keyword', + }, + 'zeek.files.local_orig': { + category: 'zeek', + description: + 'If the source of this file is a network connection, this field indicates if the data originated from the local network or not. ', + name: 'zeek.files.local_orig', + type: 'boolean', + }, + 'zeek.files.is_orig': { + category: 'zeek', + description: + 'If the source of this file is a network connection, this field indicates if the file is being sent by the originator of the connection or the responder. ', + name: 'zeek.files.is_orig', + type: 'boolean', + }, + 'zeek.files.duration': { + category: 'zeek', + description: 'The duration the file was analyzed for. Not the duration of the session. ', + name: 'zeek.files.duration', + type: 'double', + }, + 'zeek.files.seen_bytes': { + category: 'zeek', + description: 'Number of bytes provided to the file analysis engine for the file. ', + name: 'zeek.files.seen_bytes', + type: 'long', + }, + 'zeek.files.total_bytes': { + category: 'zeek', + description: 'Total number of bytes that are supposed to comprise the full file. ', + name: 'zeek.files.total_bytes', + type: 'long', + }, + 'zeek.files.missing_bytes': { + category: 'zeek', + description: + 'The number of bytes in the file stream that were completely missed during the process of analysis. ', + name: 'zeek.files.missing_bytes', + type: 'long', + }, + 'zeek.files.overflow_bytes': { + category: 'zeek', + description: + "The number of bytes in the file stream that were not delivered to stream file analyzers. This could be overlapping bytes or bytes that couldn't be reassembled. ", + name: 'zeek.files.overflow_bytes', + type: 'long', + }, + 'zeek.files.timedout': { + category: 'zeek', + description: 'Whether the file analysis timed out at least once for the file. ', + name: 'zeek.files.timedout', + type: 'boolean', + }, + 'zeek.files.parent_fuid': { + category: 'zeek', + description: + 'Identifier associated with a container file from which this one was extracted as part of the file analysis. ', + name: 'zeek.files.parent_fuid', + type: 'keyword', + }, + 'zeek.files.md5': { + category: 'zeek', + description: 'An MD5 digest of the file contents. ', + name: 'zeek.files.md5', + type: 'keyword', + }, + 'zeek.files.sha1': { + category: 'zeek', + description: 'A SHA1 digest of the file contents. ', + name: 'zeek.files.sha1', + type: 'keyword', + }, + 'zeek.files.sha256': { + category: 'zeek', + description: 'A SHA256 digest of the file contents. ', + name: 'zeek.files.sha256', + type: 'keyword', + }, + 'zeek.files.extracted': { + category: 'zeek', + description: 'Local filename of extracted file. ', + name: 'zeek.files.extracted', + type: 'keyword', + }, + 'zeek.files.extracted_cutoff': { + category: 'zeek', + description: + 'Indicate whether the file being extracted was cut off hence not extracted completely. ', + name: 'zeek.files.extracted_cutoff', + type: 'boolean', + }, + 'zeek.files.extracted_size': { + category: 'zeek', + description: 'The number of bytes extracted to disk. ', + name: 'zeek.files.extracted_size', + type: 'long', + }, + 'zeek.files.entropy': { + category: 'zeek', + description: 'The information density of the contents of the file. ', + name: 'zeek.files.entropy', + type: 'double', + }, + 'zeek.ftp.user': { + category: 'zeek', + description: 'User name for the current FTP session. ', + name: 'zeek.ftp.user', + type: 'keyword', + }, + 'zeek.ftp.password': { + category: 'zeek', + description: 'Password for the current FTP session if captured. ', + name: 'zeek.ftp.password', + type: 'keyword', + }, + 'zeek.ftp.command': { + category: 'zeek', + description: 'Command given by the client. ', + name: 'zeek.ftp.command', + type: 'keyword', + }, + 'zeek.ftp.arg': { + category: 'zeek', + description: 'Argument for the command if one is given. ', + name: 'zeek.ftp.arg', + type: 'keyword', + }, + 'zeek.ftp.file.size': { + category: 'zeek', + description: 'Size of the file if the command indicates a file transfer. ', + name: 'zeek.ftp.file.size', + type: 'long', + }, + 'zeek.ftp.file.mime_type': { + category: 'zeek', + description: 'Sniffed mime type of file. ', + name: 'zeek.ftp.file.mime_type', + type: 'keyword', + }, + 'zeek.ftp.file.fuid': { + category: 'zeek', + description: '(present if base/protocols/ftp/files.bro is loaded) File unique ID. ', + name: 'zeek.ftp.file.fuid', + type: 'keyword', + }, + 'zeek.ftp.reply.code': { + category: 'zeek', + description: 'Reply code from the server in response to the command. ', + name: 'zeek.ftp.reply.code', + type: 'integer', + }, + 'zeek.ftp.reply.msg': { + category: 'zeek', + description: 'Reply message from the server in response to the command. ', + name: 'zeek.ftp.reply.msg', + type: 'keyword', + }, + 'zeek.ftp.data_channel.passive': { + category: 'zeek', + description: 'Whether PASV mode is toggled for control channel. ', + name: 'zeek.ftp.data_channel.passive', + type: 'boolean', + }, + 'zeek.ftp.data_channel.originating_host': { + category: 'zeek', + description: 'The host that will be initiating the data connection. ', + name: 'zeek.ftp.data_channel.originating_host', + type: 'ip', + }, + 'zeek.ftp.data_channel.response_host': { + category: 'zeek', + description: 'The host that will be accepting the data connection. ', + name: 'zeek.ftp.data_channel.response_host', + type: 'ip', + }, + 'zeek.ftp.data_channel.response_port': { + category: 'zeek', + description: 'The port at which the acceptor is listening for the data connection. ', + name: 'zeek.ftp.data_channel.response_port', + type: 'integer', + }, + 'zeek.ftp.cwd': { + category: 'zeek', + description: + "Current working directory that this session is in. By making the default value '.', we can indicate that unless something more concrete is discovered that the existing but unknown directory is ok to use. ", + name: 'zeek.ftp.cwd', + type: 'keyword', + }, + 'zeek.ftp.cmdarg.cmd': { + category: 'zeek', + description: 'Command. ', + name: 'zeek.ftp.cmdarg.cmd', + type: 'keyword', + }, + 'zeek.ftp.cmdarg.arg': { + category: 'zeek', + description: 'Argument for the command if one was given. ', + name: 'zeek.ftp.cmdarg.arg', + type: 'keyword', + }, + 'zeek.ftp.cmdarg.seq': { + category: 'zeek', + description: 'Counter to track how many commands have been executed. ', + name: 'zeek.ftp.cmdarg.seq', + type: 'integer', + }, + 'zeek.ftp.pending_commands': { + category: 'zeek', + description: + 'Queue for commands that have been sent but not yet responded to are tracked here. ', + name: 'zeek.ftp.pending_commands', + type: 'integer', + }, + 'zeek.ftp.passive': { + category: 'zeek', + description: 'Indicates if the session is in active or passive mode. ', + name: 'zeek.ftp.passive', + type: 'boolean', + }, + 'zeek.ftp.capture_password': { + category: 'zeek', + description: 'Determines if the password will be captured for this request. ', + name: 'zeek.ftp.capture_password', + type: 'boolean', + }, + 'zeek.ftp.last_auth_requested': { + category: 'zeek', + description: + 'present if base/protocols/ftp/gridftp.bro is loaded. Last authentication/security mechanism that was used. ', + name: 'zeek.ftp.last_auth_requested', + type: 'keyword', + }, + 'zeek.http.trans_depth': { + category: 'zeek', + description: + 'Represents the pipelined depth into the connection of this request/response transaction. ', + name: 'zeek.http.trans_depth', + type: 'integer', + }, + 'zeek.http.status_msg': { + category: 'zeek', + description: 'Status message returned by the server. ', + name: 'zeek.http.status_msg', + type: 'keyword', + }, + 'zeek.http.info_code': { + category: 'zeek', + description: 'Last seen 1xx informational reply code returned by the server. ', + name: 'zeek.http.info_code', + type: 'integer', + }, + 'zeek.http.info_msg': { + category: 'zeek', + description: 'Last seen 1xx informational reply message returned by the server. ', + name: 'zeek.http.info_msg', + type: 'keyword', + }, + 'zeek.http.tags': { + category: 'zeek', + description: + 'A set of indicators of various attributes discovered and related to a particular request/response pair. ', + name: 'zeek.http.tags', + type: 'keyword', + }, + 'zeek.http.password': { + category: 'zeek', + description: 'Password if basic-auth is performed for the request. ', + name: 'zeek.http.password', + type: 'keyword', + }, + 'zeek.http.captured_password': { + category: 'zeek', + description: 'Determines if the password will be captured for this request. ', + name: 'zeek.http.captured_password', + type: 'boolean', + }, + 'zeek.http.proxied': { + category: 'zeek', + description: 'All of the headers that may indicate if the HTTP request was proxied. ', + name: 'zeek.http.proxied', + type: 'keyword', + }, + 'zeek.http.range_request': { + category: 'zeek', + description: 'Indicates if this request can assume 206 partial content in response. ', + name: 'zeek.http.range_request', + type: 'boolean', + }, + 'zeek.http.client_header_names': { + category: 'zeek', + description: + 'The vector of HTTP header names sent by the client. No header values are included here, just the header names. ', + name: 'zeek.http.client_header_names', + type: 'keyword', + }, + 'zeek.http.server_header_names': { + category: 'zeek', + description: + 'The vector of HTTP header names sent by the server. No header values are included here, just the header names. ', + name: 'zeek.http.server_header_names', + type: 'keyword', + }, + 'zeek.http.orig_fuids': { + category: 'zeek', + description: 'An ordered vector of file unique IDs from the originator. ', + name: 'zeek.http.orig_fuids', + type: 'keyword', + }, + 'zeek.http.orig_mime_types': { + category: 'zeek', + description: 'An ordered vector of mime types from the originator. ', + name: 'zeek.http.orig_mime_types', + type: 'keyword', + }, + 'zeek.http.orig_filenames': { + category: 'zeek', + description: 'An ordered vector of filenames from the originator. ', + name: 'zeek.http.orig_filenames', + type: 'keyword', + }, + 'zeek.http.resp_fuids': { + category: 'zeek', + description: 'An ordered vector of file unique IDs from the responder. ', + name: 'zeek.http.resp_fuids', + type: 'keyword', + }, + 'zeek.http.resp_mime_types': { + category: 'zeek', + description: 'An ordered vector of mime types from the responder. ', + name: 'zeek.http.resp_mime_types', + type: 'keyword', + }, + 'zeek.http.resp_filenames': { + category: 'zeek', + description: 'An ordered vector of filenames from the responder. ', + name: 'zeek.http.resp_filenames', + type: 'keyword', + }, + 'zeek.http.orig_mime_depth': { + category: 'zeek', + description: 'Current number of MIME entities in the HTTP request message body. ', + name: 'zeek.http.orig_mime_depth', + type: 'integer', + }, + 'zeek.http.resp_mime_depth': { + category: 'zeek', + description: 'Current number of MIME entities in the HTTP response message body. ', + name: 'zeek.http.resp_mime_depth', + type: 'integer', + }, + 'zeek.intel.seen.indicator': { + category: 'zeek', + description: 'The intelligence indicator. ', + name: 'zeek.intel.seen.indicator', + type: 'keyword', + }, + 'zeek.intel.seen.indicator_type': { + category: 'zeek', + description: 'The type of data the indicator represents. ', + name: 'zeek.intel.seen.indicator_type', + type: 'keyword', + }, + 'zeek.intel.seen.host': { + category: 'zeek', + description: 'If the indicator type was Intel::ADDR, then this field will be present. ', + name: 'zeek.intel.seen.host', + type: 'keyword', + }, + 'zeek.intel.seen.conn': { + category: 'zeek', + description: + 'If the data was discovered within a connection, the connection record should go here to give context to the data. ', + name: 'zeek.intel.seen.conn', + type: 'keyword', + }, + 'zeek.intel.seen.where': { + category: 'zeek', + description: 'Where the data was discovered. ', + name: 'zeek.intel.seen.where', + type: 'keyword', + }, + 'zeek.intel.seen.node': { + category: 'zeek', + description: 'The name of the node where the match was discovered. ', + name: 'zeek.intel.seen.node', + type: 'keyword', + }, + 'zeek.intel.seen.uid': { + category: 'zeek', + description: + 'If the data was discovered within a connection, the connection uid should go here to give context to the data. If the conn field is provided, this will be automatically filled out. ', + name: 'zeek.intel.seen.uid', + type: 'keyword', + }, + 'zeek.intel.seen.f': { + category: 'zeek', + description: + 'If the data was discovered within a file, the file record should go here to provide context to the data. ', + name: 'zeek.intel.seen.f', + type: 'object', + }, + 'zeek.intel.seen.fuid': { + category: 'zeek', + description: + 'If the data was discovered within a file, the file uid should go here to provide context to the data. If the file record f is provided, this will be automatically filled out. ', + name: 'zeek.intel.seen.fuid', + type: 'keyword', + }, + 'zeek.intel.matched': { + category: 'zeek', + description: 'Event to represent a match in the intelligence data from data that was seen. ', + name: 'zeek.intel.matched', + type: 'keyword', + }, + 'zeek.intel.sources': { + category: 'zeek', + description: 'Sources which supplied data for this match. ', + name: 'zeek.intel.sources', + type: 'keyword', + }, + 'zeek.intel.fuid': { + category: 'zeek', + description: + 'If a file was associated with this intelligence hit, this is the uid for the file. ', + name: 'zeek.intel.fuid', + type: 'keyword', + }, + 'zeek.intel.file_mime_type': { + category: 'zeek', + description: + 'A mime type if the intelligence hit is related to a file. If the $f field is provided this will be automatically filled out. ', + name: 'zeek.intel.file_mime_type', + type: 'keyword', + }, + 'zeek.intel.file_desc': { + category: 'zeek', + description: + 'Frequently files can be described to give a bit more context. If the $f field is provided this field will be automatically filled out. ', + name: 'zeek.intel.file_desc', + type: 'keyword', + }, + 'zeek.irc.nick': { + category: 'zeek', + description: 'Nickname given for the connection. ', + name: 'zeek.irc.nick', + type: 'keyword', + }, + 'zeek.irc.user': { + category: 'zeek', + description: 'Username given for the connection. ', + name: 'zeek.irc.user', + type: 'keyword', + }, + 'zeek.irc.command': { + category: 'zeek', + description: 'Command given by the client. ', + name: 'zeek.irc.command', + type: 'keyword', + }, + 'zeek.irc.value': { + category: 'zeek', + description: 'Value for the command given by the client. ', + name: 'zeek.irc.value', + type: 'keyword', + }, + 'zeek.irc.addl': { + category: 'zeek', + description: 'Any additional data for the command. ', + name: 'zeek.irc.addl', + type: 'keyword', + }, + 'zeek.irc.dcc.file.name': { + category: 'zeek', + description: 'Present if base/protocols/irc/dcc-send.bro is loaded. DCC filename requested. ', + name: 'zeek.irc.dcc.file.name', + type: 'keyword', + }, + 'zeek.irc.dcc.file.size': { + category: 'zeek', + description: + 'Present if base/protocols/irc/dcc-send.bro is loaded. Size of the DCC transfer as indicated by the sender. ', + name: 'zeek.irc.dcc.file.size', + type: 'long', + }, + 'zeek.irc.dcc.mime_type': { + category: 'zeek', + description: + 'present if base/protocols/irc/dcc-send.bro is loaded. Sniffed mime type of the file. ', + name: 'zeek.irc.dcc.mime_type', + type: 'keyword', + }, + 'zeek.irc.fuid': { + category: 'zeek', + description: 'present if base/protocols/irc/files.bro is loaded. File unique ID. ', + name: 'zeek.irc.fuid', + type: 'keyword', + }, + 'zeek.kerberos.request_type': { + category: 'zeek', + description: 'Request type - Authentication Service (AS) or Ticket Granting Service (TGS). ', + name: 'zeek.kerberos.request_type', + type: 'keyword', + }, + 'zeek.kerberos.client': { + category: 'zeek', + description: 'Client name. ', + name: 'zeek.kerberos.client', + type: 'keyword', + }, + 'zeek.kerberos.service': { + category: 'zeek', + description: 'Service name. ', + name: 'zeek.kerberos.service', + type: 'keyword', + }, + 'zeek.kerberos.success': { + category: 'zeek', + description: 'Request result. ', + name: 'zeek.kerberos.success', + type: 'boolean', + }, + 'zeek.kerberos.error.code': { + category: 'zeek', + description: 'Error code. ', + name: 'zeek.kerberos.error.code', + type: 'integer', + }, + 'zeek.kerberos.error.msg': { + category: 'zeek', + description: 'Error message. ', + name: 'zeek.kerberos.error.msg', + type: 'keyword', + }, + 'zeek.kerberos.valid.from': { + category: 'zeek', + description: 'Ticket valid from. ', + name: 'zeek.kerberos.valid.from', + type: 'date', + }, + 'zeek.kerberos.valid.until': { + category: 'zeek', + description: 'Ticket valid until. ', + name: 'zeek.kerberos.valid.until', + type: 'date', + }, + 'zeek.kerberos.valid.days': { + category: 'zeek', + description: 'Number of days the ticket is valid for. ', + name: 'zeek.kerberos.valid.days', + type: 'integer', + }, + 'zeek.kerberos.cipher': { + category: 'zeek', + description: 'Ticket encryption type. ', + name: 'zeek.kerberos.cipher', + type: 'keyword', + }, + 'zeek.kerberos.forwardable': { + category: 'zeek', + description: 'Forwardable ticket requested. ', + name: 'zeek.kerberos.forwardable', + type: 'boolean', + }, + 'zeek.kerberos.renewable': { + category: 'zeek', + description: 'Renewable ticket requested. ', + name: 'zeek.kerberos.renewable', + type: 'boolean', + }, + 'zeek.kerberos.ticket.auth': { + category: 'zeek', + description: 'Hash of ticket used to authorize request/transaction. ', + name: 'zeek.kerberos.ticket.auth', + type: 'keyword', + }, + 'zeek.kerberos.ticket.new': { + category: 'zeek', + description: 'Hash of ticket returned by the KDC. ', + name: 'zeek.kerberos.ticket.new', + type: 'keyword', + }, + 'zeek.kerberos.cert.client.value': { + category: 'zeek', + description: 'Client certificate. ', + name: 'zeek.kerberos.cert.client.value', + type: 'keyword', + }, + 'zeek.kerberos.cert.client.fuid': { + category: 'zeek', + description: 'File unique ID of client cert. ', + name: 'zeek.kerberos.cert.client.fuid', + type: 'keyword', + }, + 'zeek.kerberos.cert.client.subject': { + category: 'zeek', + description: 'Subject of client certificate. ', + name: 'zeek.kerberos.cert.client.subject', + type: 'keyword', + }, + 'zeek.kerberos.cert.server.value': { + category: 'zeek', + description: 'Server certificate. ', + name: 'zeek.kerberos.cert.server.value', + type: 'keyword', + }, + 'zeek.kerberos.cert.server.fuid': { + category: 'zeek', + description: 'File unique ID of server certificate. ', + name: 'zeek.kerberos.cert.server.fuid', + type: 'keyword', + }, + 'zeek.kerberos.cert.server.subject': { + category: 'zeek', + description: 'Subject of server certificate. ', + name: 'zeek.kerberos.cert.server.subject', + type: 'keyword', + }, + 'zeek.modbus.function': { + category: 'zeek', + description: 'The name of the function message that was sent. ', + name: 'zeek.modbus.function', + type: 'keyword', + }, + 'zeek.modbus.exception': { + category: 'zeek', + description: 'The exception if the response was a failure. ', + name: 'zeek.modbus.exception', + type: 'keyword', + }, + 'zeek.modbus.track_address': { + category: 'zeek', + description: + 'Present if policy/protocols/modbus/track-memmap.bro is loaded. Modbus track address. ', + name: 'zeek.modbus.track_address', + type: 'integer', + }, + 'zeek.mysql.cmd': { + category: 'zeek', + description: 'The command that was issued. ', + name: 'zeek.mysql.cmd', + type: 'keyword', + }, + 'zeek.mysql.arg': { + category: 'zeek', + description: 'The argument issued to the command. ', + name: 'zeek.mysql.arg', + type: 'keyword', + }, + 'zeek.mysql.success': { + category: 'zeek', + description: 'Whether the command succeeded. ', + name: 'zeek.mysql.success', + type: 'boolean', + }, + 'zeek.mysql.rows': { + category: 'zeek', + description: 'The number of affected rows, if any. ', + name: 'zeek.mysql.rows', + type: 'integer', + }, + 'zeek.mysql.response': { + category: 'zeek', + description: 'Server message, if any. ', + name: 'zeek.mysql.response', + type: 'keyword', + }, + 'zeek.notice.connection_id': { + category: 'zeek', + description: 'Identifier of the related connection session. ', + name: 'zeek.notice.connection_id', + type: 'keyword', + }, + 'zeek.notice.icmp_id': { + category: 'zeek', + description: 'Identifier of the related ICMP session. ', + name: 'zeek.notice.icmp_id', + type: 'keyword', + }, + 'zeek.notice.file.id': { + category: 'zeek', + description: 'An identifier associated with a single file that is related to this notice. ', + name: 'zeek.notice.file.id', + type: 'keyword', + }, + 'zeek.notice.file.parent_id': { + category: 'zeek', + description: 'Identifier associated with a container file from which this one was extracted. ', + name: 'zeek.notice.file.parent_id', + type: 'keyword', + }, + 'zeek.notice.file.source': { + category: 'zeek', + description: + 'An identification of the source of the file data. E.g. it may be a network protocol over which it was transferred, or a local file path which was read, or some other input source. ', + name: 'zeek.notice.file.source', + type: 'keyword', + }, + 'zeek.notice.file.mime_type': { + category: 'zeek', + description: 'A mime type if the notice is related to a file. ', + name: 'zeek.notice.file.mime_type', + type: 'keyword', + }, + 'zeek.notice.file.is_orig': { + category: 'zeek', + description: + 'If the source of this file is a network connection, this field indicates if the file is being sent by the originator of the connection or the responder. ', + name: 'zeek.notice.file.is_orig', + type: 'boolean', + }, + 'zeek.notice.file.seen_bytes': { + category: 'zeek', + description: 'Number of bytes provided to the file analysis engine for the file. ', + name: 'zeek.notice.file.seen_bytes', + type: 'long', + }, + 'zeek.notice.ffile.total_bytes': { + category: 'zeek', + description: 'Total number of bytes that are supposed to comprise the full file. ', + name: 'zeek.notice.ffile.total_bytes', + type: 'long', + }, + 'zeek.notice.file.missing_bytes': { + category: 'zeek', + description: + 'The number of bytes in the file stream that were completely missed during the process of analysis. ', + name: 'zeek.notice.file.missing_bytes', + type: 'long', + }, + 'zeek.notice.file.overflow_bytes': { + category: 'zeek', + description: + "The number of bytes in the file stream that were not delivered to stream file analyzers. This could be overlapping bytes or bytes that couldn't be reassembled. ", + name: 'zeek.notice.file.overflow_bytes', + type: 'long', + }, + 'zeek.notice.fuid': { + category: 'zeek', + description: 'A file unique ID if this notice is related to a file. ', + name: 'zeek.notice.fuid', + type: 'keyword', + }, + 'zeek.notice.note': { + category: 'zeek', + description: 'The type of the notice. ', + name: 'zeek.notice.note', + type: 'keyword', + }, + 'zeek.notice.msg': { + category: 'zeek', + description: 'The human readable message for the notice. ', + name: 'zeek.notice.msg', + type: 'keyword', + }, + 'zeek.notice.sub': { + category: 'zeek', + description: 'The human readable sub-message. ', + name: 'zeek.notice.sub', + type: 'keyword', + }, + 'zeek.notice.n': { + category: 'zeek', + description: 'Associated count, or a status code. ', + name: 'zeek.notice.n', + type: 'long', + }, + 'zeek.notice.peer_name': { + category: 'zeek', + description: 'Name of remote peer that raised this notice. ', + name: 'zeek.notice.peer_name', + type: 'keyword', + }, + 'zeek.notice.peer_descr': { + category: 'zeek', + description: 'Textual description for the peer that raised this notice. ', + name: 'zeek.notice.peer_descr', + type: 'text', + }, + 'zeek.notice.actions': { + category: 'zeek', + description: 'The actions which have been applied to this notice. ', + name: 'zeek.notice.actions', + type: 'keyword', + }, + 'zeek.notice.email_body_sections': { + category: 'zeek', + description: + 'By adding chunks of text into this element, other scripts can expand on notices that are being emailed. ', + name: 'zeek.notice.email_body_sections', + type: 'text', + }, + 'zeek.notice.email_delay_tokens': { + category: 'zeek', + description: + 'Adding a string token to this set will cause the built-in emailing functionality to delay sending the email either the token has been removed or the email has been delayed for the specified time duration. ', + name: 'zeek.notice.email_delay_tokens', + type: 'keyword', + }, + 'zeek.notice.identifier': { + category: 'zeek', + description: + 'This field is provided when a notice is generated for the purpose of deduplicating notices. ', + name: 'zeek.notice.identifier', + type: 'keyword', + }, + 'zeek.notice.suppress_for': { + category: 'zeek', + description: + 'This field indicates the length of time that this unique notice should be suppressed. ', + name: 'zeek.notice.suppress_for', + type: 'double', + }, + 'zeek.notice.dropped': { + category: 'zeek', + description: 'Indicate if the source IP address was dropped and denied network access. ', + name: 'zeek.notice.dropped', + type: 'boolean', + }, + 'zeek.ntlm.domain': { + category: 'zeek', + description: 'Domain name given by the client. ', + name: 'zeek.ntlm.domain', + type: 'keyword', + }, + 'zeek.ntlm.hostname': { + category: 'zeek', + description: 'Hostname given by the client. ', + name: 'zeek.ntlm.hostname', + type: 'keyword', + }, + 'zeek.ntlm.success': { + category: 'zeek', + description: 'Indicate whether or not the authentication was successful. ', + name: 'zeek.ntlm.success', + type: 'boolean', + }, + 'zeek.ntlm.username': { + category: 'zeek', + description: 'Username given by the client. ', + name: 'zeek.ntlm.username', + type: 'keyword', + }, + 'zeek.ntlm.server.name.dns': { + category: 'zeek', + description: 'DNS name given by the server in a CHALLENGE. ', + name: 'zeek.ntlm.server.name.dns', + type: 'keyword', + }, + 'zeek.ntlm.server.name.netbios': { + category: 'zeek', + description: 'NetBIOS name given by the server in a CHALLENGE. ', + name: 'zeek.ntlm.server.name.netbios', + type: 'keyword', + }, + 'zeek.ntlm.server.name.tree': { + category: 'zeek', + description: 'Tree name given by the server in a CHALLENGE. ', + name: 'zeek.ntlm.server.name.tree', + type: 'keyword', + }, + 'zeek.ocsp.file_id': { + category: 'zeek', + description: 'File id of the OCSP reply. ', + name: 'zeek.ocsp.file_id', + type: 'keyword', + }, + 'zeek.ocsp.hash.algorithm': { + category: 'zeek', + description: 'Hash algorithm used to generate issuerNameHash and issuerKeyHash. ', + name: 'zeek.ocsp.hash.algorithm', + type: 'keyword', + }, + 'zeek.ocsp.hash.issuer.name': { + category: 'zeek', + description: "Hash of the issuer's distingueshed name. ", + name: 'zeek.ocsp.hash.issuer.name', + type: 'keyword', + }, + 'zeek.ocsp.hash.issuer.key': { + category: 'zeek', + description: "Hash of the issuer's public key. ", + name: 'zeek.ocsp.hash.issuer.key', + type: 'keyword', + }, + 'zeek.ocsp.serial_number': { + category: 'zeek', + description: 'Serial number of the affected certificate. ', + name: 'zeek.ocsp.serial_number', + type: 'keyword', + }, + 'zeek.ocsp.status': { + category: 'zeek', + description: 'Status of the affected certificate. ', + name: 'zeek.ocsp.status', + type: 'keyword', + }, + 'zeek.ocsp.revoke.time': { + category: 'zeek', + description: 'Time at which the certificate was revoked. ', + name: 'zeek.ocsp.revoke.time', + type: 'date', + }, + 'zeek.ocsp.revoke.reason': { + category: 'zeek', + description: 'Reason for which the certificate was revoked. ', + name: 'zeek.ocsp.revoke.reason', + type: 'keyword', + }, + 'zeek.ocsp.update.this': { + category: 'zeek', + description: 'The time at which the status being shows is known to have been correct. ', + name: 'zeek.ocsp.update.this', + type: 'date', + }, + 'zeek.ocsp.update.next': { + category: 'zeek', + description: + 'The latest time at which new information about the status of the certificate will be available. ', + name: 'zeek.ocsp.update.next', + type: 'date', + }, + 'zeek.pe.client': { + category: 'zeek', + description: "The client's version string. ", + name: 'zeek.pe.client', + type: 'keyword', + }, + 'zeek.pe.id': { + category: 'zeek', + description: 'File id of this portable executable file. ', + name: 'zeek.pe.id', + type: 'keyword', + }, + 'zeek.pe.machine': { + category: 'zeek', + description: 'The target machine that the file was compiled for. ', + name: 'zeek.pe.machine', + type: 'keyword', + }, + 'zeek.pe.compile_time': { + category: 'zeek', + description: 'The time that the file was created at. ', + name: 'zeek.pe.compile_time', + type: 'date', + }, + 'zeek.pe.os': { + category: 'zeek', + description: 'The required operating system. ', + name: 'zeek.pe.os', + type: 'keyword', + }, + 'zeek.pe.subsystem': { + category: 'zeek', + description: 'The subsystem that is required to run this file. ', + name: 'zeek.pe.subsystem', + type: 'keyword', + }, + 'zeek.pe.is_exe': { + category: 'zeek', + description: 'Is the file an executable, or just an object file? ', + name: 'zeek.pe.is_exe', + type: 'boolean', + }, + 'zeek.pe.is_64bit': { + category: 'zeek', + description: 'Is the file a 64-bit executable? ', + name: 'zeek.pe.is_64bit', + type: 'boolean', + }, + 'zeek.pe.uses_aslr': { + category: 'zeek', + description: 'Does the file support Address Space Layout Randomization? ', + name: 'zeek.pe.uses_aslr', + type: 'boolean', + }, + 'zeek.pe.uses_dep': { + category: 'zeek', + description: 'Does the file support Data Execution Prevention? ', + name: 'zeek.pe.uses_dep', + type: 'boolean', + }, + 'zeek.pe.uses_code_integrity': { + category: 'zeek', + description: 'Does the file enforce code integrity checks? ', + name: 'zeek.pe.uses_code_integrity', + type: 'boolean', + }, + 'zeek.pe.uses_seh': { + category: 'zeek', + description: 'Does the file use structured exception handing? ', + name: 'zeek.pe.uses_seh', + type: 'boolean', + }, + 'zeek.pe.has_import_table': { + category: 'zeek', + description: 'Does the file have an import table? ', + name: 'zeek.pe.has_import_table', + type: 'boolean', + }, + 'zeek.pe.has_export_table': { + category: 'zeek', + description: 'Does the file have an export table? ', + name: 'zeek.pe.has_export_table', + type: 'boolean', + }, + 'zeek.pe.has_cert_table': { + category: 'zeek', + description: 'Does the file have an attribute certificate table? ', + name: 'zeek.pe.has_cert_table', + type: 'boolean', + }, + 'zeek.pe.has_debug_data': { + category: 'zeek', + description: 'Does the file have a debug table? ', + name: 'zeek.pe.has_debug_data', + type: 'boolean', + }, + 'zeek.pe.section_names': { + category: 'zeek', + description: 'The names of the sections, in order. ', + name: 'zeek.pe.section_names', + type: 'keyword', + }, + 'zeek.radius.username': { + category: 'zeek', + description: 'The username, if present. ', + name: 'zeek.radius.username', + type: 'keyword', + }, + 'zeek.radius.mac': { + category: 'zeek', + description: 'MAC address, if present. ', + name: 'zeek.radius.mac', + type: 'keyword', + }, + 'zeek.radius.framed_addr': { + category: 'zeek', + description: + 'The address given to the network access server, if present. This is only a hint from the RADIUS server and the network access server is not required to honor the address. ', + name: 'zeek.radius.framed_addr', + type: 'ip', + }, + 'zeek.radius.remote_ip': { + category: 'zeek', + description: + 'Remote IP address, if present. This is collected from the Tunnel-Client-Endpoint attribute. ', + name: 'zeek.radius.remote_ip', + type: 'ip', + }, + 'zeek.radius.connect_info': { + category: 'zeek', + description: 'Connect info, if present. ', + name: 'zeek.radius.connect_info', + type: 'keyword', + }, + 'zeek.radius.reply_msg': { + category: 'zeek', + description: + 'Reply message from the server challenge. This is frequently shown to the user authenticating. ', + name: 'zeek.radius.reply_msg', + type: 'keyword', + }, + 'zeek.radius.result': { + category: 'zeek', + description: 'Successful or failed authentication. ', + name: 'zeek.radius.result', + type: 'keyword', + }, + 'zeek.radius.ttl': { + category: 'zeek', + description: + 'The duration between the first request and either the "Access-Accept" message or an error. If the field is empty, it means that either the request or response was not seen. ', + name: 'zeek.radius.ttl', + type: 'integer', + }, + 'zeek.radius.logged': { + category: 'zeek', + description: 'Whether this has already been logged and can be ignored. ', + name: 'zeek.radius.logged', + type: 'boolean', + }, + 'zeek.rdp.cookie': { + category: 'zeek', + description: 'Cookie value used by the client machine. This is typically a username. ', + name: 'zeek.rdp.cookie', + type: 'keyword', + }, + 'zeek.rdp.result': { + category: 'zeek', + description: + "Status result for the connection. It's a mix between RDP negotation failure messages and GCC server create response messages. ", + name: 'zeek.rdp.result', + type: 'keyword', + }, + 'zeek.rdp.security_protocol': { + category: 'zeek', + description: 'Security protocol chosen by the server. ', + name: 'zeek.rdp.security_protocol', + type: 'keyword', + }, + 'zeek.rdp.keyboard_layout': { + category: 'zeek', + description: 'Keyboard layout (language) of the client machine. ', + name: 'zeek.rdp.keyboard_layout', + type: 'keyword', + }, + 'zeek.rdp.client.build': { + category: 'zeek', + description: 'RDP client version used by the client machine. ', + name: 'zeek.rdp.client.build', + type: 'keyword', + }, + 'zeek.rdp.client.client_name': { + category: 'zeek', + description: 'Name of the client machine. ', + name: 'zeek.rdp.client.client_name', + type: 'keyword', + }, + 'zeek.rdp.client.product_id': { + category: 'zeek', + description: 'Product ID of the client machine. ', + name: 'zeek.rdp.client.product_id', + type: 'keyword', + }, + 'zeek.rdp.desktop.width': { + category: 'zeek', + description: 'Desktop width of the client machine. ', + name: 'zeek.rdp.desktop.width', + type: 'integer', + }, + 'zeek.rdp.desktop.height': { + category: 'zeek', + description: 'Desktop height of the client machine. ', + name: 'zeek.rdp.desktop.height', + type: 'integer', + }, + 'zeek.rdp.desktop.color_depth': { + category: 'zeek', + description: 'The color depth requested by the client in the high_color_depth field. ', + name: 'zeek.rdp.desktop.color_depth', + type: 'keyword', + }, + 'zeek.rdp.cert.type': { + category: 'zeek', + description: + 'If the connection is being encrypted with native RDP encryption, this is the type of cert being used. ', + name: 'zeek.rdp.cert.type', + type: 'keyword', + }, + 'zeek.rdp.cert.count': { + category: 'zeek', + description: 'The number of certs seen. X.509 can transfer an entire certificate chain. ', + name: 'zeek.rdp.cert.count', + type: 'integer', + }, + 'zeek.rdp.cert.permanent': { + category: 'zeek', + description: + 'Indicates if the provided certificate or certificate chain is permanent or temporary. ', + name: 'zeek.rdp.cert.permanent', + type: 'boolean', + }, + 'zeek.rdp.encryption.level': { + category: 'zeek', + description: 'Encryption level of the connection. ', + name: 'zeek.rdp.encryption.level', + type: 'keyword', + }, + 'zeek.rdp.encryption.method': { + category: 'zeek', + description: 'Encryption method of the connection. ', + name: 'zeek.rdp.encryption.method', + type: 'keyword', + }, + 'zeek.rdp.done': { + category: 'zeek', + description: 'Track status of logging RDP connections. ', + name: 'zeek.rdp.done', + type: 'boolean', + }, + 'zeek.rdp.ssl': { + category: 'zeek', + description: + '(present if policy/protocols/rdp/indicate_ssl.bro is loaded) Flag the connection if it was seen over SSL. ', + name: 'zeek.rdp.ssl', + type: 'boolean', + }, + 'zeek.rfb.version.client.major': { + category: 'zeek', + description: 'Major version of the client. ', + name: 'zeek.rfb.version.client.major', + type: 'keyword', + }, + 'zeek.rfb.version.client.minor': { + category: 'zeek', + description: 'Minor version of the client. ', + name: 'zeek.rfb.version.client.minor', + type: 'keyword', + }, + 'zeek.rfb.version.server.major': { + category: 'zeek', + description: 'Major version of the server. ', + name: 'zeek.rfb.version.server.major', + type: 'keyword', + }, + 'zeek.rfb.version.server.minor': { + category: 'zeek', + description: 'Minor version of the server. ', + name: 'zeek.rfb.version.server.minor', + type: 'keyword', + }, + 'zeek.rfb.auth.success': { + category: 'zeek', + description: 'Whether or not authentication was successful. ', + name: 'zeek.rfb.auth.success', + type: 'boolean', + }, + 'zeek.rfb.auth.method': { + category: 'zeek', + description: 'Identifier of authentication method used. ', + name: 'zeek.rfb.auth.method', + type: 'keyword', + }, + 'zeek.rfb.share_flag': { + category: 'zeek', + description: 'Whether the client has an exclusive or a shared session. ', + name: 'zeek.rfb.share_flag', + type: 'boolean', + }, + 'zeek.rfb.desktop_name': { + category: 'zeek', + description: 'Name of the screen that is being shared. ', + name: 'zeek.rfb.desktop_name', + type: 'keyword', + }, + 'zeek.rfb.width': { + category: 'zeek', + description: 'Width of the screen that is being shared. ', + name: 'zeek.rfb.width', + type: 'integer', + }, + 'zeek.rfb.height': { + category: 'zeek', + description: 'Height of the screen that is being shared. ', + name: 'zeek.rfb.height', + type: 'integer', + }, + 'zeek.sip.transaction_depth': { + category: 'zeek', + description: + 'Represents the pipelined depth into the connection of this request/response transaction. ', + name: 'zeek.sip.transaction_depth', + type: 'integer', + }, + 'zeek.sip.sequence.method': { + category: 'zeek', + description: 'Verb used in the SIP request (INVITE, REGISTER etc.). ', + name: 'zeek.sip.sequence.method', + type: 'keyword', + }, + 'zeek.sip.sequence.number': { + category: 'zeek', + description: 'Contents of the CSeq: header from the client. ', + name: 'zeek.sip.sequence.number', + type: 'keyword', + }, + 'zeek.sip.uri': { + category: 'zeek', + description: 'URI used in the request. ', + name: 'zeek.sip.uri', + type: 'keyword', + }, + 'zeek.sip.date': { + category: 'zeek', + description: 'Contents of the Date: header from the client. ', + name: 'zeek.sip.date', + type: 'keyword', + }, + 'zeek.sip.request.from': { + category: 'zeek', + description: + "Contents of the request From: header Note: The tag= value that's usually appended to the sender is stripped off and not logged. ", + name: 'zeek.sip.request.from', + type: 'keyword', + }, + 'zeek.sip.request.to': { + category: 'zeek', + description: 'Contents of the To: header. ', + name: 'zeek.sip.request.to', + type: 'keyword', + }, + 'zeek.sip.request.path': { + category: 'zeek', + description: 'The client message transmission path, as extracted from the headers. ', + name: 'zeek.sip.request.path', + type: 'keyword', + }, + 'zeek.sip.request.body_length': { + category: 'zeek', + description: 'Contents of the Content-Length: header from the client. ', + name: 'zeek.sip.request.body_length', + type: 'long', + }, + 'zeek.sip.response.from': { + category: 'zeek', + description: + "Contents of the response From: header Note: The tag= value that's usually appended to the sender is stripped off and not logged. ", + name: 'zeek.sip.response.from', + type: 'keyword', + }, + 'zeek.sip.response.to': { + category: 'zeek', + description: 'Contents of the response To: header. ', + name: 'zeek.sip.response.to', + type: 'keyword', + }, + 'zeek.sip.response.path': { + category: 'zeek', + description: 'The server message transmission path, as extracted from the headers. ', + name: 'zeek.sip.response.path', + type: 'keyword', + }, + 'zeek.sip.response.body_length': { + category: 'zeek', + description: 'Contents of the Content-Length: header from the server. ', + name: 'zeek.sip.response.body_length', + type: 'long', + }, + 'zeek.sip.reply_to': { + category: 'zeek', + description: 'Contents of the Reply-To: header. ', + name: 'zeek.sip.reply_to', + type: 'keyword', + }, + 'zeek.sip.call_id': { + category: 'zeek', + description: 'Contents of the Call-ID: header from the client. ', + name: 'zeek.sip.call_id', + type: 'keyword', + }, + 'zeek.sip.subject': { + category: 'zeek', + description: 'Contents of the Subject: header from the client. ', + name: 'zeek.sip.subject', + type: 'keyword', + }, + 'zeek.sip.user_agent': { + category: 'zeek', + description: 'Contents of the User-Agent: header from the client. ', + name: 'zeek.sip.user_agent', + type: 'keyword', + }, + 'zeek.sip.status.code': { + category: 'zeek', + description: 'Status code returned by the server. ', + name: 'zeek.sip.status.code', + type: 'integer', + }, + 'zeek.sip.status.msg': { + category: 'zeek', + description: 'Status message returned by the server. ', + name: 'zeek.sip.status.msg', + type: 'keyword', + }, + 'zeek.sip.warning': { + category: 'zeek', + description: 'Contents of the Warning: header. ', + name: 'zeek.sip.warning', + type: 'keyword', + }, + 'zeek.sip.content_type': { + category: 'zeek', + description: 'Contents of the Content-Type: header from the server. ', + name: 'zeek.sip.content_type', + type: 'keyword', + }, + 'zeek.smb_cmd.command': { + category: 'zeek', + description: 'The command sent by the client. ', + name: 'zeek.smb_cmd.command', + type: 'keyword', + }, + 'zeek.smb_cmd.sub_command': { + category: 'zeek', + description: 'The subcommand sent by the client, if present. ', + name: 'zeek.smb_cmd.sub_command', + type: 'keyword', + }, + 'zeek.smb_cmd.argument': { + category: 'zeek', + description: 'Command argument sent by the client, if any. ', + name: 'zeek.smb_cmd.argument', + type: 'keyword', + }, + 'zeek.smb_cmd.status': { + category: 'zeek', + description: "Server reply to the client's command. ", + name: 'zeek.smb_cmd.status', + type: 'keyword', + }, + 'zeek.smb_cmd.rtt': { + category: 'zeek', + description: 'Round trip time from the request to the response. ', + name: 'zeek.smb_cmd.rtt', + type: 'double', + }, + 'zeek.smb_cmd.version': { + category: 'zeek', + description: 'Version of SMB for the command. ', + name: 'zeek.smb_cmd.version', + type: 'keyword', + }, + 'zeek.smb_cmd.username': { + category: 'zeek', + description: 'Authenticated username, if available. ', + name: 'zeek.smb_cmd.username', + type: 'keyword', + }, + 'zeek.smb_cmd.tree': { + category: 'zeek', + description: + 'If this is related to a tree, this is the tree that was used for the current command. ', + name: 'zeek.smb_cmd.tree', + type: 'keyword', + }, + 'zeek.smb_cmd.tree_service': { + category: 'zeek', + description: 'The type of tree (disk share, printer share, named pipe, etc.). ', + name: 'zeek.smb_cmd.tree_service', + type: 'keyword', + }, + 'zeek.smb_cmd.file.name': { + category: 'zeek', + description: 'Filename if one was seen. ', + name: 'zeek.smb_cmd.file.name', + type: 'keyword', + }, + 'zeek.smb_cmd.file.action': { + category: 'zeek', + description: 'Action this log record represents. ', + name: 'zeek.smb_cmd.file.action', + type: 'keyword', + }, + 'zeek.smb_cmd.file.uid': { + category: 'zeek', + description: 'UID of the referenced file. ', + name: 'zeek.smb_cmd.file.uid', + type: 'keyword', + }, + 'zeek.smb_cmd.file.host.tx': { + category: 'zeek', + description: 'Address of the transmitting host. ', + name: 'zeek.smb_cmd.file.host.tx', + type: 'ip', + }, + 'zeek.smb_cmd.file.host.rx': { + category: 'zeek', + description: 'Address of the receiving host. ', + name: 'zeek.smb_cmd.file.host.rx', + type: 'ip', + }, + 'zeek.smb_cmd.smb1_offered_dialects': { + category: 'zeek', + description: + 'Present if base/protocols/smb/smb1-main.bro is loaded. Dialects offered by the client. ', + name: 'zeek.smb_cmd.smb1_offered_dialects', + type: 'keyword', + }, + 'zeek.smb_cmd.smb2_offered_dialects': { + category: 'zeek', + description: + 'Present if base/protocols/smb/smb2-main.bro is loaded. Dialects offered by the client. ', + name: 'zeek.smb_cmd.smb2_offered_dialects', + type: 'integer', + }, + 'zeek.smb_files.action': { + category: 'zeek', + description: 'Action this log record represents. ', + name: 'zeek.smb_files.action', + type: 'keyword', + }, + 'zeek.smb_files.fid': { + category: 'zeek', + description: 'ID referencing this file. ', + name: 'zeek.smb_files.fid', + type: 'integer', + }, + 'zeek.smb_files.name': { + category: 'zeek', + description: 'Filename if one was seen. ', + name: 'zeek.smb_files.name', + type: 'keyword', + }, + 'zeek.smb_files.path': { + category: 'zeek', + description: 'Path pulled from the tree this file was transferred to or from. ', + name: 'zeek.smb_files.path', + type: 'keyword', + }, + 'zeek.smb_files.previous_name': { + category: 'zeek', + description: "If the rename action was seen, this will be the file's previous name. ", + name: 'zeek.smb_files.previous_name', + type: 'keyword', + }, + 'zeek.smb_files.size': { + category: 'zeek', + description: 'Byte size of the file. ', + name: 'zeek.smb_files.size', + type: 'long', + }, + 'zeek.smb_files.times.accessed': { + category: 'zeek', + description: "The file's access time. ", + name: 'zeek.smb_files.times.accessed', + type: 'date', + }, + 'zeek.smb_files.times.changed': { + category: 'zeek', + description: "The file's change time. ", + name: 'zeek.smb_files.times.changed', + type: 'date', + }, + 'zeek.smb_files.times.created': { + category: 'zeek', + description: "The file's create time. ", + name: 'zeek.smb_files.times.created', + type: 'date', + }, + 'zeek.smb_files.times.modified': { + category: 'zeek', + description: "The file's modify time. ", + name: 'zeek.smb_files.times.modified', + type: 'date', + }, + 'zeek.smb_files.uuid': { + category: 'zeek', + description: 'UUID referencing this file if DCE/RPC. ', + name: 'zeek.smb_files.uuid', + type: 'keyword', + }, + 'zeek.smb_mapping.path': { + category: 'zeek', + description: 'Name of the tree path. ', + name: 'zeek.smb_mapping.path', + type: 'keyword', + }, + 'zeek.smb_mapping.service': { + category: 'zeek', + description: 'The type of resource of the tree (disk share, printer share, named pipe, etc.). ', + name: 'zeek.smb_mapping.service', + type: 'keyword', + }, + 'zeek.smb_mapping.native_file_system': { + category: 'zeek', + description: 'File system of the tree. ', + name: 'zeek.smb_mapping.native_file_system', + type: 'keyword', + }, + 'zeek.smb_mapping.share_type': { + category: 'zeek', + description: + 'If this is SMB2, a share type will be included. For SMB1, the type of share will be deduced and included as well. ', + name: 'zeek.smb_mapping.share_type', + type: 'keyword', + }, + 'zeek.smtp.transaction_depth': { + category: 'zeek', + description: + 'A count to represent the depth of this message transaction in a single connection where multiple messages were transferred. ', + name: 'zeek.smtp.transaction_depth', + type: 'integer', + }, + 'zeek.smtp.helo': { + category: 'zeek', + description: 'Contents of the Helo header. ', + name: 'zeek.smtp.helo', + type: 'keyword', + }, + 'zeek.smtp.mail_from': { + category: 'zeek', + description: 'Email addresses found in the MAIL FROM header. ', + name: 'zeek.smtp.mail_from', + type: 'keyword', + }, + 'zeek.smtp.rcpt_to': { + category: 'zeek', + description: 'Email addresses found in the RCPT TO header. ', + name: 'zeek.smtp.rcpt_to', + type: 'keyword', + }, + 'zeek.smtp.date': { + category: 'zeek', + description: 'Contents of the Date header. ', + name: 'zeek.smtp.date', + type: 'date', + }, + 'zeek.smtp.from': { + category: 'zeek', + description: 'Contents of the From header. ', + name: 'zeek.smtp.from', + type: 'keyword', + }, + 'zeek.smtp.to': { + category: 'zeek', + description: 'Contents of the To header. ', + name: 'zeek.smtp.to', + type: 'keyword', + }, + 'zeek.smtp.cc': { + category: 'zeek', + description: 'Contents of the CC header. ', + name: 'zeek.smtp.cc', + type: 'keyword', + }, + 'zeek.smtp.reply_to': { + category: 'zeek', + description: 'Contents of the ReplyTo header. ', + name: 'zeek.smtp.reply_to', + type: 'keyword', + }, + 'zeek.smtp.msg_id': { + category: 'zeek', + description: 'Contents of the MsgID header. ', + name: 'zeek.smtp.msg_id', + type: 'keyword', + }, + 'zeek.smtp.in_reply_to': { + category: 'zeek', + description: 'Contents of the In-Reply-To header. ', + name: 'zeek.smtp.in_reply_to', + type: 'keyword', + }, + 'zeek.smtp.subject': { + category: 'zeek', + description: 'Contents of the Subject header. ', + name: 'zeek.smtp.subject', + type: 'keyword', + }, + 'zeek.smtp.x_originating_ip': { + category: 'zeek', + description: 'Contents of the X-Originating-IP header. ', + name: 'zeek.smtp.x_originating_ip', + type: 'keyword', + }, + 'zeek.smtp.first_received': { + category: 'zeek', + description: 'Contents of the first Received header. ', + name: 'zeek.smtp.first_received', + type: 'keyword', + }, + 'zeek.smtp.second_received': { + category: 'zeek', + description: 'Contents of the second Received header. ', + name: 'zeek.smtp.second_received', + type: 'keyword', + }, + 'zeek.smtp.last_reply': { + category: 'zeek', + description: 'The last message that the server sent to the client. ', + name: 'zeek.smtp.last_reply', + type: 'keyword', + }, + 'zeek.smtp.path': { + category: 'zeek', + description: 'The message transmission path, as extracted from the headers. ', + name: 'zeek.smtp.path', + type: 'ip', + }, + 'zeek.smtp.user_agent': { + category: 'zeek', + description: 'Value of the User-Agent header from the client. ', + name: 'zeek.smtp.user_agent', + type: 'keyword', + }, + 'zeek.smtp.tls': { + category: 'zeek', + description: 'Indicates that the connection has switched to using TLS. ', + name: 'zeek.smtp.tls', + type: 'boolean', + }, + 'zeek.smtp.process_received_from': { + category: 'zeek', + description: 'Indicates if the "Received: from" headers should still be processed. ', + name: 'zeek.smtp.process_received_from', + type: 'boolean', + }, + 'zeek.smtp.has_client_activity': { + category: 'zeek', + description: 'Indicates if client activity has been seen, but not yet logged. ', + name: 'zeek.smtp.has_client_activity', + type: 'boolean', + }, + 'zeek.smtp.fuids': { + category: 'zeek', + description: + '(present if base/protocols/smtp/files.bro is loaded) An ordered vector of file unique IDs seen attached to the message. ', + name: 'zeek.smtp.fuids', + type: 'keyword', + }, + 'zeek.smtp.is_webmail': { + category: 'zeek', + description: 'Indicates if the message was sent through a webmail interface. ', + name: 'zeek.smtp.is_webmail', + type: 'boolean', + }, + 'zeek.snmp.duration': { + category: 'zeek', + description: + 'The amount of time between the first packet beloning to the SNMP session and the latest one seen. ', + name: 'zeek.snmp.duration', + type: 'double', + }, + 'zeek.snmp.version': { + category: 'zeek', + description: 'The version of SNMP being used. ', + name: 'zeek.snmp.version', + type: 'keyword', + }, + 'zeek.snmp.community': { + category: 'zeek', + description: + "The community string of the first SNMP packet associated with the session. This is used as part of SNMP's (v1 and v2c) administrative/security framework. See RFC 1157 or RFC 1901. ", + name: 'zeek.snmp.community', + type: 'keyword', + }, + 'zeek.snmp.get.requests': { + category: 'zeek', + description: + 'The number of variable bindings in GetRequest/GetNextRequest PDUs seen for the session. ', + name: 'zeek.snmp.get.requests', + type: 'integer', + }, + 'zeek.snmp.get.bulk_requests': { + category: 'zeek', + description: 'The number of variable bindings in GetBulkRequest PDUs seen for the session. ', + name: 'zeek.snmp.get.bulk_requests', + type: 'integer', + }, + 'zeek.snmp.get.responses': { + category: 'zeek', + description: + 'The number of variable bindings in GetResponse/Response PDUs seen for the session. ', + name: 'zeek.snmp.get.responses', + type: 'integer', + }, + 'zeek.snmp.set.requests': { + category: 'zeek', + description: 'The number of variable bindings in SetRequest PDUs seen for the session. ', + name: 'zeek.snmp.set.requests', + type: 'integer', + }, + 'zeek.snmp.display_string': { + category: 'zeek', + description: 'A system description of the SNMP responder endpoint. ', + name: 'zeek.snmp.display_string', + type: 'keyword', + }, + 'zeek.snmp.up_since': { + category: 'zeek', + description: "The time at which the SNMP responder endpoint claims it's been up since. ", + name: 'zeek.snmp.up_since', + type: 'date', + }, + 'zeek.socks.version': { + category: 'zeek', + description: 'Protocol version of SOCKS. ', + name: 'zeek.socks.version', + type: 'integer', + }, + 'zeek.socks.user': { + category: 'zeek', + description: 'Username used to request a login to the proxy. ', + name: 'zeek.socks.user', + type: 'keyword', + }, + 'zeek.socks.password': { + category: 'zeek', + description: 'Password used to request a login to the proxy. ', + name: 'zeek.socks.password', + type: 'keyword', + }, + 'zeek.socks.status': { + category: 'zeek', + description: 'Server status for the attempt at using the proxy. ', + name: 'zeek.socks.status', + type: 'keyword', + }, + 'zeek.socks.request.host': { + category: 'zeek', + description: 'Client requested SOCKS address. Could be an address, a name or both. ', + name: 'zeek.socks.request.host', + type: 'keyword', + }, + 'zeek.socks.request.port': { + category: 'zeek', + description: 'Client requested port. ', + name: 'zeek.socks.request.port', + type: 'integer', + }, + 'zeek.socks.bound.host': { + category: 'zeek', + description: 'Server bound address. Could be an address, a name or both. ', + name: 'zeek.socks.bound.host', + type: 'keyword', + }, + 'zeek.socks.bound.port': { + category: 'zeek', + description: 'Server bound port. ', + name: 'zeek.socks.bound.port', + type: 'integer', + }, + 'zeek.socks.capture_password': { + category: 'zeek', + description: 'Determines if the password will be captured for this request. ', + name: 'zeek.socks.capture_password', + type: 'boolean', + }, + 'zeek.ssh.client': { + category: 'zeek', + description: "The client's version string. ", + name: 'zeek.ssh.client', + type: 'keyword', + }, + 'zeek.ssh.direction': { + category: 'zeek', + description: + 'Direction of the connection. If the client was a local host logging into an external host, this would be OUTBOUND. INBOUND would be set for the opposite situation. ', + name: 'zeek.ssh.direction', + type: 'keyword', + }, + 'zeek.ssh.host_key': { + category: 'zeek', + description: "The server's key thumbprint. ", + name: 'zeek.ssh.host_key', + type: 'keyword', + }, + 'zeek.ssh.server': { + category: 'zeek', + description: "The server's version string. ", + name: 'zeek.ssh.server', + type: 'keyword', + }, + 'zeek.ssh.version': { + category: 'zeek', + description: 'SSH major version (1 or 2). ', + name: 'zeek.ssh.version', + type: 'integer', + }, + 'zeek.ssh.algorithm.cipher': { + category: 'zeek', + description: 'The encryption algorithm in use. ', + name: 'zeek.ssh.algorithm.cipher', + type: 'keyword', + }, + 'zeek.ssh.algorithm.compression': { + category: 'zeek', + description: 'The compression algorithm in use. ', + name: 'zeek.ssh.algorithm.compression', + type: 'keyword', + }, + 'zeek.ssh.algorithm.host_key': { + category: 'zeek', + description: "The server host key's algorithm. ", + name: 'zeek.ssh.algorithm.host_key', + type: 'keyword', + }, + 'zeek.ssh.algorithm.key_exchange': { + category: 'zeek', + description: 'The key exchange algorithm in use. ', + name: 'zeek.ssh.algorithm.key_exchange', + type: 'keyword', + }, + 'zeek.ssh.algorithm.mac': { + category: 'zeek', + description: 'The signing (MAC) algorithm in use. ', + name: 'zeek.ssh.algorithm.mac', + type: 'keyword', + }, + 'zeek.ssh.auth.attempts': { + category: 'zeek', + description: + "The number of authentication attemps we observed. There's always at least one, since some servers might support no authentication at all. It's important to note that not all of these are failures, since some servers require two-factor auth (e.g. password AND pubkey). ", + name: 'zeek.ssh.auth.attempts', + type: 'integer', + }, + 'zeek.ssh.auth.success': { + category: 'zeek', + description: 'Authentication result. ', + name: 'zeek.ssh.auth.success', + type: 'boolean', + }, + 'zeek.ssl.version': { + category: 'zeek', + description: 'SSL/TLS version that was logged. ', + name: 'zeek.ssl.version', + type: 'keyword', + }, + 'zeek.ssl.cipher': { + category: 'zeek', + description: 'SSL/TLS cipher suite that was logged. ', + name: 'zeek.ssl.cipher', + type: 'keyword', + }, + 'zeek.ssl.curve': { + category: 'zeek', + description: 'Elliptic curve that was logged when using ECDH/ECDHE. ', + name: 'zeek.ssl.curve', + type: 'keyword', + }, + 'zeek.ssl.resumed': { + category: 'zeek', + description: + 'Flag to indicate if the session was resumed reusing the key material exchanged in an earlier connection. ', + name: 'zeek.ssl.resumed', + type: 'boolean', + }, + 'zeek.ssl.next_protocol': { + category: 'zeek', + description: + 'Next protocol the server chose using the application layer next protocol extension. ', + name: 'zeek.ssl.next_protocol', + type: 'keyword', + }, + 'zeek.ssl.established': { + category: 'zeek', + description: 'Flag to indicate if this ssl session has been established successfully. ', + name: 'zeek.ssl.established', + type: 'boolean', + }, + 'zeek.ssl.validation.status': { + category: 'zeek', + description: 'Result of certificate validation for this connection. ', + name: 'zeek.ssl.validation.status', + type: 'keyword', + }, + 'zeek.ssl.validation.code': { + category: 'zeek', + description: + 'Result of certificate validation for this connection, given as OpenSSL validation code. ', + name: 'zeek.ssl.validation.code', + type: 'keyword', + }, + 'zeek.ssl.last_alert': { + category: 'zeek', + description: 'Last alert that was seen during the connection. ', + name: 'zeek.ssl.last_alert', + type: 'keyword', + }, + 'zeek.ssl.server.name': { + category: 'zeek', + description: + 'Value of the Server Name Indicator SSL/TLS extension. It indicates the server name that the client was requesting. ', + name: 'zeek.ssl.server.name', + type: 'keyword', + }, + 'zeek.ssl.server.cert_chain': { + category: 'zeek', + description: + 'Chain of certificates offered by the server to validate its complete signing chain. ', + name: 'zeek.ssl.server.cert_chain', + type: 'keyword', + }, + 'zeek.ssl.server.cert_chain_fuids': { + category: 'zeek', + description: + 'An ordered vector of certificate file identifiers for the certificates offered by the server. ', + name: 'zeek.ssl.server.cert_chain_fuids', + type: 'keyword', + }, + 'zeek.ssl.server.issuer.common_name': { + category: 'zeek', + description: 'Common name of the signer of the X.509 certificate offered by the server. ', + name: 'zeek.ssl.server.issuer.common_name', + type: 'keyword', + }, + 'zeek.ssl.server.issuer.country': { + category: 'zeek', + description: 'Country code of the signer of the X.509 certificate offered by the server. ', + name: 'zeek.ssl.server.issuer.country', + type: 'keyword', + }, + 'zeek.ssl.server.issuer.locality': { + category: 'zeek', + description: 'Locality of the signer of the X.509 certificate offered by the server. ', + name: 'zeek.ssl.server.issuer.locality', + type: 'keyword', + }, + 'zeek.ssl.server.issuer.organization': { + category: 'zeek', + description: 'Organization of the signer of the X.509 certificate offered by the server. ', + name: 'zeek.ssl.server.issuer.organization', + type: 'keyword', + }, + 'zeek.ssl.server.issuer.organizational_unit': { + category: 'zeek', + description: + 'Organizational unit of the signer of the X.509 certificate offered by the server. ', + name: 'zeek.ssl.server.issuer.organizational_unit', + type: 'keyword', + }, + 'zeek.ssl.server.issuer.state': { + category: 'zeek', + description: + 'State or province name of the signer of the X.509 certificate offered by the server. ', + name: 'zeek.ssl.server.issuer.state', + type: 'keyword', + }, + 'zeek.ssl.server.subject.common_name': { + category: 'zeek', + description: 'Common name of the X.509 certificate offered by the server. ', + name: 'zeek.ssl.server.subject.common_name', + type: 'keyword', + }, + 'zeek.ssl.server.subject.country': { + category: 'zeek', + description: 'Country code of the X.509 certificate offered by the server. ', + name: 'zeek.ssl.server.subject.country', + type: 'keyword', + }, + 'zeek.ssl.server.subject.locality': { + category: 'zeek', + description: 'Locality of the X.509 certificate offered by the server. ', + name: 'zeek.ssl.server.subject.locality', + type: 'keyword', + }, + 'zeek.ssl.server.subject.organization': { + category: 'zeek', + description: 'Organization of the X.509 certificate offered by the server. ', + name: 'zeek.ssl.server.subject.organization', + type: 'keyword', + }, + 'zeek.ssl.server.subject.organizational_unit': { + category: 'zeek', + description: 'Organizational unit of the X.509 certificate offered by the server. ', + name: 'zeek.ssl.server.subject.organizational_unit', + type: 'keyword', + }, + 'zeek.ssl.server.subject.state': { + category: 'zeek', + description: 'State or province name of the X.509 certificate offered by the server. ', + name: 'zeek.ssl.server.subject.state', + type: 'keyword', + }, + 'zeek.ssl.client.cert_chain': { + category: 'zeek', + description: + 'Chain of certificates offered by the client to validate its complete signing chain. ', + name: 'zeek.ssl.client.cert_chain', + type: 'keyword', + }, + 'zeek.ssl.client.cert_chain_fuids': { + category: 'zeek', + description: + 'An ordered vector of certificate file identifiers for the certificates offered by the client. ', + name: 'zeek.ssl.client.cert_chain_fuids', + type: 'keyword', + }, + 'zeek.ssl.client.issuer.common_name': { + category: 'zeek', + description: 'Common name of the signer of the X.509 certificate offered by the client. ', + name: 'zeek.ssl.client.issuer.common_name', + type: 'keyword', + }, + 'zeek.ssl.client.issuer.country': { + category: 'zeek', + description: 'Country code of the signer of the X.509 certificate offered by the client. ', + name: 'zeek.ssl.client.issuer.country', + type: 'keyword', + }, + 'zeek.ssl.client.issuer.locality': { + category: 'zeek', + description: 'Locality of the signer of the X.509 certificate offered by the client. ', + name: 'zeek.ssl.client.issuer.locality', + type: 'keyword', + }, + 'zeek.ssl.client.issuer.organization': { + category: 'zeek', + description: 'Organization of the signer of the X.509 certificate offered by the client. ', + name: 'zeek.ssl.client.issuer.organization', + type: 'keyword', + }, + 'zeek.ssl.client.issuer.organizational_unit': { + category: 'zeek', + description: + 'Organizational unit of the signer of the X.509 certificate offered by the client. ', + name: 'zeek.ssl.client.issuer.organizational_unit', + type: 'keyword', + }, + 'zeek.ssl.client.issuer.state': { + category: 'zeek', + description: + 'State or province name of the signer of the X.509 certificate offered by the client. ', + name: 'zeek.ssl.client.issuer.state', + type: 'keyword', + }, + 'zeek.ssl.client.subject.common_name': { + category: 'zeek', + description: 'Common name of the X.509 certificate offered by the client. ', + name: 'zeek.ssl.client.subject.common_name', + type: 'keyword', + }, + 'zeek.ssl.client.subject.country': { + category: 'zeek', + description: 'Country code of the X.509 certificate offered by the client. ', + name: 'zeek.ssl.client.subject.country', + type: 'keyword', + }, + 'zeek.ssl.client.subject.locality': { + category: 'zeek', + description: 'Locality of the X.509 certificate offered by the client. ', + name: 'zeek.ssl.client.subject.locality', + type: 'keyword', + }, + 'zeek.ssl.client.subject.organization': { + category: 'zeek', + description: 'Organization of the X.509 certificate offered by the client. ', + name: 'zeek.ssl.client.subject.organization', + type: 'keyword', + }, + 'zeek.ssl.client.subject.organizational_unit': { + category: 'zeek', + description: 'Organizational unit of the X.509 certificate offered by the client. ', + name: 'zeek.ssl.client.subject.organizational_unit', + type: 'keyword', + }, + 'zeek.ssl.client.subject.state': { + category: 'zeek', + description: 'State or province name of the X.509 certificate offered by the client. ', + name: 'zeek.ssl.client.subject.state', + type: 'keyword', + }, + 'zeek.stats.peer': { + category: 'zeek', + description: 'Peer that generated this log. Mostly for clusters. ', + name: 'zeek.stats.peer', + type: 'keyword', + }, + 'zeek.stats.memory': { + category: 'zeek', + description: 'Amount of memory currently in use in MB. ', + name: 'zeek.stats.memory', + type: 'integer', + }, + 'zeek.stats.packets.processed': { + category: 'zeek', + description: 'Number of packets processed since the last stats interval. ', + name: 'zeek.stats.packets.processed', + type: 'long', + }, + 'zeek.stats.packets.dropped': { + category: 'zeek', + description: + 'Number of packets dropped since the last stats interval if reading live traffic. ', + name: 'zeek.stats.packets.dropped', + type: 'long', + }, + 'zeek.stats.packets.received': { + category: 'zeek', + description: + 'Number of packets seen on the link since the last stats interval if reading live traffic. ', + name: 'zeek.stats.packets.received', + type: 'long', + }, + 'zeek.stats.bytes.received': { + category: 'zeek', + description: 'Number of bytes received since the last stats interval if reading live traffic. ', + name: 'zeek.stats.bytes.received', + type: 'long', + }, + 'zeek.stats.connections.tcp.active': { + category: 'zeek', + description: 'TCP connections currently in memory. ', + name: 'zeek.stats.connections.tcp.active', + type: 'integer', + }, + 'zeek.stats.connections.tcp.count': { + category: 'zeek', + description: 'TCP connections seen since last stats interval. ', + name: 'zeek.stats.connections.tcp.count', + type: 'integer', + }, + 'zeek.stats.connections.udp.active': { + category: 'zeek', + description: 'UDP connections currently in memory. ', + name: 'zeek.stats.connections.udp.active', + type: 'integer', + }, + 'zeek.stats.connections.udp.count': { + category: 'zeek', + description: 'UDP connections seen since last stats interval. ', + name: 'zeek.stats.connections.udp.count', + type: 'integer', + }, + 'zeek.stats.connections.icmp.active': { + category: 'zeek', + description: 'ICMP connections currently in memory. ', + name: 'zeek.stats.connections.icmp.active', + type: 'integer', + }, + 'zeek.stats.connections.icmp.count': { + category: 'zeek', + description: 'ICMP connections seen since last stats interval. ', + name: 'zeek.stats.connections.icmp.count', + type: 'integer', + }, + 'zeek.stats.events.processed': { + category: 'zeek', + description: 'Number of events processed since the last stats interval. ', + name: 'zeek.stats.events.processed', + type: 'integer', + }, + 'zeek.stats.events.queued': { + category: 'zeek', + description: 'Number of events that have been queued since the last stats interval. ', + name: 'zeek.stats.events.queued', + type: 'integer', + }, + 'zeek.stats.timers.count': { + category: 'zeek', + description: 'Number of timers scheduled since last stats interval. ', + name: 'zeek.stats.timers.count', + type: 'integer', + }, + 'zeek.stats.timers.active': { + category: 'zeek', + description: 'Current number of scheduled timers. ', + name: 'zeek.stats.timers.active', + type: 'integer', + }, + 'zeek.stats.files.count': { + category: 'zeek', + description: 'Number of files seen since last stats interval. ', + name: 'zeek.stats.files.count', + type: 'integer', + }, + 'zeek.stats.files.active': { + category: 'zeek', + description: 'Current number of files actively being seen. ', + name: 'zeek.stats.files.active', + type: 'integer', + }, + 'zeek.stats.dns_requests.count': { + category: 'zeek', + description: 'Number of DNS requests seen since last stats interval. ', + name: 'zeek.stats.dns_requests.count', + type: 'integer', + }, + 'zeek.stats.dns_requests.active': { + category: 'zeek', + description: 'Current number of DNS requests awaiting a reply. ', + name: 'zeek.stats.dns_requests.active', + type: 'integer', + }, + 'zeek.stats.reassembly_size.tcp': { + category: 'zeek', + description: 'Current size of TCP data in reassembly. ', + name: 'zeek.stats.reassembly_size.tcp', + type: 'integer', + }, + 'zeek.stats.reassembly_size.file': { + category: 'zeek', + description: 'Current size of File data in reassembly. ', + name: 'zeek.stats.reassembly_size.file', + type: 'integer', + }, + 'zeek.stats.reassembly_size.frag': { + category: 'zeek', + description: 'Current size of packet fragment data in reassembly. ', + name: 'zeek.stats.reassembly_size.frag', + type: 'integer', + }, + 'zeek.stats.reassembly_size.unknown': { + category: 'zeek', + description: 'Current size of unknown data in reassembly (this is only PIA buffer right now). ', + name: 'zeek.stats.reassembly_size.unknown', + type: 'integer', + }, + 'zeek.stats.timestamp_lag': { + category: 'zeek', + description: 'Lag between the wall clock and packet timestamps if reading live traffic. ', + name: 'zeek.stats.timestamp_lag', + type: 'integer', + }, + 'zeek.syslog.facility': { + category: 'zeek', + description: 'Syslog facility for the message. ', + name: 'zeek.syslog.facility', + type: 'keyword', + }, + 'zeek.syslog.severity': { + category: 'zeek', + description: 'Syslog severity for the message. ', + name: 'zeek.syslog.severity', + type: 'keyword', + }, + 'zeek.syslog.message': { + category: 'zeek', + description: 'The plain text message. ', + name: 'zeek.syslog.message', + type: 'keyword', + }, + 'zeek.tunnel.type': { + category: 'zeek', + description: 'The type of tunnel. ', + name: 'zeek.tunnel.type', + type: 'keyword', + }, + 'zeek.tunnel.action': { + category: 'zeek', + description: 'The type of activity that occurred. ', + name: 'zeek.tunnel.action', + type: 'keyword', + }, + 'zeek.weird.name': { + category: 'zeek', + description: 'The name of the weird that occurred. ', + name: 'zeek.weird.name', + type: 'keyword', + }, + 'zeek.weird.additional_info': { + category: 'zeek', + description: 'Additional information accompanying the weird if any. ', + name: 'zeek.weird.additional_info', + type: 'keyword', + }, + 'zeek.weird.notice': { + category: 'zeek', + description: 'Indicate if this weird was also turned into a notice. ', + name: 'zeek.weird.notice', + type: 'boolean', + }, + 'zeek.weird.peer': { + category: 'zeek', + description: + 'The peer that originated this weird. This is helpful in cluster deployments if a particular cluster node is having trouble to help identify which node is having trouble. ', + name: 'zeek.weird.peer', + type: 'keyword', + }, + 'zeek.weird.identifier': { + category: 'zeek', + description: + 'This field is to be provided when a weird is generated for the purpose of deduplicating weirds. The identifier string should be unique for a single instance of the weird. This field is used to define when a weird is conceptually a duplicate of a previous weird. ', + name: 'zeek.weird.identifier', + type: 'keyword', + }, + 'zeek.x509.id': { + category: 'zeek', + description: 'File id of this certificate. ', + name: 'zeek.x509.id', + type: 'keyword', + }, + 'zeek.x509.certificate.version': { + category: 'zeek', + description: 'Version number. ', + name: 'zeek.x509.certificate.version', + type: 'integer', + }, + 'zeek.x509.certificate.serial': { + category: 'zeek', + description: 'Serial number. ', + name: 'zeek.x509.certificate.serial', + type: 'keyword', + }, + 'zeek.x509.certificate.subject.country': { + category: 'zeek', + description: 'Country provided in the certificate subject. ', + name: 'zeek.x509.certificate.subject.country', + type: 'keyword', + }, + 'zeek.x509.certificate.subject.common_name': { + category: 'zeek', + description: 'Common name provided in the certificate subject. ', + name: 'zeek.x509.certificate.subject.common_name', + type: 'keyword', + }, + 'zeek.x509.certificate.subject.locality': { + category: 'zeek', + description: 'Locality provided in the certificate subject. ', + name: 'zeek.x509.certificate.subject.locality', + type: 'keyword', + }, + 'zeek.x509.certificate.subject.organization': { + category: 'zeek', + description: 'Organization provided in the certificate subject. ', + name: 'zeek.x509.certificate.subject.organization', + type: 'keyword', + }, + 'zeek.x509.certificate.subject.organizational_unit': { + category: 'zeek', + description: 'Organizational unit provided in the certificate subject. ', + name: 'zeek.x509.certificate.subject.organizational_unit', + type: 'keyword', + }, + 'zeek.x509.certificate.subject.state': { + category: 'zeek', + description: 'State or province provided in the certificate subject. ', + name: 'zeek.x509.certificate.subject.state', + type: 'keyword', + }, + 'zeek.x509.certificate.issuer.country': { + category: 'zeek', + description: 'Country provided in the certificate issuer field. ', + name: 'zeek.x509.certificate.issuer.country', + type: 'keyword', + }, + 'zeek.x509.certificate.issuer.common_name': { + category: 'zeek', + description: 'Common name provided in the certificate issuer field. ', + name: 'zeek.x509.certificate.issuer.common_name', + type: 'keyword', + }, + 'zeek.x509.certificate.issuer.locality': { + category: 'zeek', + description: 'Locality provided in the certificate issuer field. ', + name: 'zeek.x509.certificate.issuer.locality', + type: 'keyword', + }, + 'zeek.x509.certificate.issuer.organization': { + category: 'zeek', + description: 'Organization provided in the certificate issuer field. ', + name: 'zeek.x509.certificate.issuer.organization', + type: 'keyword', + }, + 'zeek.x509.certificate.issuer.organizational_unit': { + category: 'zeek', + description: 'Organizational unit provided in the certificate issuer field. ', + name: 'zeek.x509.certificate.issuer.organizational_unit', + type: 'keyword', + }, + 'zeek.x509.certificate.issuer.state': { + category: 'zeek', + description: 'State or province provided in the certificate issuer field. ', + name: 'zeek.x509.certificate.issuer.state', + type: 'keyword', + }, + 'zeek.x509.certificate.common_name': { + category: 'zeek', + description: 'Last (most specific) common name. ', + name: 'zeek.x509.certificate.common_name', + type: 'keyword', + }, + 'zeek.x509.certificate.valid.from': { + category: 'zeek', + description: 'Timestamp before when certificate is not valid. ', + name: 'zeek.x509.certificate.valid.from', + type: 'date', + }, + 'zeek.x509.certificate.valid.until': { + category: 'zeek', + description: 'Timestamp after when certificate is not valid. ', + name: 'zeek.x509.certificate.valid.until', + type: 'date', + }, + 'zeek.x509.certificate.key.algorithm': { + category: 'zeek', + description: 'Name of the key algorithm. ', + name: 'zeek.x509.certificate.key.algorithm', + type: 'keyword', + }, + 'zeek.x509.certificate.key.type': { + category: 'zeek', + description: 'Key type, if key parseable by openssl (either rsa, dsa or ec). ', + name: 'zeek.x509.certificate.key.type', + type: 'keyword', + }, + 'zeek.x509.certificate.key.length': { + category: 'zeek', + description: 'Key length in bits. ', + name: 'zeek.x509.certificate.key.length', + type: 'integer', + }, + 'zeek.x509.certificate.signature_algorithm': { + category: 'zeek', + description: 'Name of the signature algorithm. ', + name: 'zeek.x509.certificate.signature_algorithm', + type: 'keyword', + }, + 'zeek.x509.certificate.exponent': { + category: 'zeek', + description: 'Exponent, if RSA-certificate. ', + name: 'zeek.x509.certificate.exponent', + type: 'keyword', + }, + 'zeek.x509.certificate.curve': { + category: 'zeek', + description: 'Curve, if EC-certificate. ', + name: 'zeek.x509.certificate.curve', + type: 'keyword', + }, + 'zeek.x509.san.dns': { + category: 'zeek', + description: 'List of DNS entries in SAN. ', + name: 'zeek.x509.san.dns', + type: 'keyword', + }, + 'zeek.x509.san.uri': { + category: 'zeek', + description: 'List of URI entries in SAN. ', + name: 'zeek.x509.san.uri', + type: 'keyword', + }, + 'zeek.x509.san.email': { + category: 'zeek', + description: 'List of email entries in SAN. ', + name: 'zeek.x509.san.email', + type: 'keyword', + }, + 'zeek.x509.san.ip': { + category: 'zeek', + description: 'List of IP entries in SAN. ', + name: 'zeek.x509.san.ip', + type: 'ip', + }, + 'zeek.x509.san.other_fields': { + category: 'zeek', + description: 'True if the certificate contained other, not recognized or parsed name fields. ', + name: 'zeek.x509.san.other_fields', + type: 'boolean', + }, + 'zeek.x509.basic_constraints.certificate_authority': { + category: 'zeek', + description: 'CA flag set or not. ', + name: 'zeek.x509.basic_constraints.certificate_authority', + type: 'boolean', + }, + 'zeek.x509.basic_constraints.path_length': { + category: 'zeek', + description: 'Maximum path length. ', + name: 'zeek.x509.basic_constraints.path_length', + type: 'integer', + }, + 'zeek.x509.log_cert': { + category: 'zeek', + description: + 'Present if policy/protocols/ssl/log-hostcerts-only.bro is loaded Logging of certificate is suppressed if set to F. ', + name: 'zeek.x509.log_cert', + type: 'boolean', + }, + 'awscloudwatch.log_group': { + category: 'awscloudwatch', + description: 'The name of the log group to which this event belongs.', + name: 'awscloudwatch.log_group', + type: 'keyword', + }, + 'awscloudwatch.log_stream': { + category: 'awscloudwatch', + description: 'The name of the log stream to which this event belongs.', + name: 'awscloudwatch.log_stream', + type: 'keyword', + }, + 'awscloudwatch.ingestion_time': { + category: 'awscloudwatch', + description: 'The time the event was ingested in AWS CloudWatch.', + name: 'awscloudwatch.ingestion_time', + type: 'keyword', + }, + 'netflow.type': { + category: 'netflow', + description: 'The type of NetFlow record described by this event. ', + name: 'netflow.type', + type: 'keyword', + }, + 'netflow.exporter.address': { + category: 'netflow', + description: "Exporter's network address in IP:port format. ", + name: 'netflow.exporter.address', + type: 'keyword', + }, + 'netflow.exporter.source_id': { + category: 'netflow', + description: 'Observation domain ID to which this record belongs. ', + name: 'netflow.exporter.source_id', + type: 'long', + }, + 'netflow.exporter.timestamp': { + category: 'netflow', + description: 'Time and date of export. ', + name: 'netflow.exporter.timestamp', + type: 'date', + }, + 'netflow.exporter.uptime_millis': { + category: 'netflow', + description: 'How long the exporter process has been running, in milliseconds. ', + name: 'netflow.exporter.uptime_millis', + type: 'long', + }, + 'netflow.exporter.version': { + category: 'netflow', + description: 'NetFlow version used. ', + name: 'netflow.exporter.version', + type: 'integer', + }, + 'netflow.octet_delta_count': { + category: 'netflow', + name: 'netflow.octet_delta_count', + type: 'long', + }, + 'netflow.packet_delta_count': { + category: 'netflow', + name: 'netflow.packet_delta_count', + type: 'long', + }, + 'netflow.delta_flow_count': { + category: 'netflow', + name: 'netflow.delta_flow_count', + type: 'long', + }, + 'netflow.protocol_identifier': { + category: 'netflow', + name: 'netflow.protocol_identifier', + type: 'short', + }, + 'netflow.ip_class_of_service': { + category: 'netflow', + name: 'netflow.ip_class_of_service', + type: 'short', + }, + 'netflow.tcp_control_bits': { + category: 'netflow', + name: 'netflow.tcp_control_bits', + type: 'integer', + }, + 'netflow.source_transport_port': { + category: 'netflow', + name: 'netflow.source_transport_port', + type: 'integer', + }, + 'netflow.source_ipv4_address': { + category: 'netflow', + name: 'netflow.source_ipv4_address', + type: 'ip', + }, + 'netflow.source_ipv4_prefix_length': { + category: 'netflow', + name: 'netflow.source_ipv4_prefix_length', + type: 'short', + }, + 'netflow.ingress_interface': { + category: 'netflow', + name: 'netflow.ingress_interface', + type: 'long', + }, + 'netflow.destination_transport_port': { + category: 'netflow', + name: 'netflow.destination_transport_port', + type: 'integer', + }, + 'netflow.destination_ipv4_address': { + category: 'netflow', + name: 'netflow.destination_ipv4_address', + type: 'ip', + }, + 'netflow.destination_ipv4_prefix_length': { + category: 'netflow', + name: 'netflow.destination_ipv4_prefix_length', + type: 'short', + }, + 'netflow.egress_interface': { + category: 'netflow', + name: 'netflow.egress_interface', + type: 'long', + }, + 'netflow.ip_next_hop_ipv4_address': { + category: 'netflow', + name: 'netflow.ip_next_hop_ipv4_address', + type: 'ip', + }, + 'netflow.bgp_source_as_number': { + category: 'netflow', + name: 'netflow.bgp_source_as_number', + type: 'long', + }, + 'netflow.bgp_destination_as_number': { + category: 'netflow', + name: 'netflow.bgp_destination_as_number', + type: 'long', + }, + 'netflow.bgp_next_hop_ipv4_address': { + category: 'netflow', + name: 'netflow.bgp_next_hop_ipv4_address', + type: 'ip', + }, + 'netflow.post_mcast_packet_delta_count': { + category: 'netflow', + name: 'netflow.post_mcast_packet_delta_count', + type: 'long', + }, + 'netflow.post_mcast_octet_delta_count': { + category: 'netflow', + name: 'netflow.post_mcast_octet_delta_count', + type: 'long', + }, + 'netflow.flow_end_sys_up_time': { + category: 'netflow', + name: 'netflow.flow_end_sys_up_time', + type: 'long', + }, + 'netflow.flow_start_sys_up_time': { + category: 'netflow', + name: 'netflow.flow_start_sys_up_time', + type: 'long', + }, + 'netflow.post_octet_delta_count': { + category: 'netflow', + name: 'netflow.post_octet_delta_count', + type: 'long', + }, + 'netflow.post_packet_delta_count': { + category: 'netflow', + name: 'netflow.post_packet_delta_count', + type: 'long', + }, + 'netflow.minimum_ip_total_length': { + category: 'netflow', + name: 'netflow.minimum_ip_total_length', + type: 'long', + }, + 'netflow.maximum_ip_total_length': { + category: 'netflow', + name: 'netflow.maximum_ip_total_length', + type: 'long', + }, + 'netflow.source_ipv6_address': { + category: 'netflow', + name: 'netflow.source_ipv6_address', + type: 'ip', + }, + 'netflow.destination_ipv6_address': { + category: 'netflow', + name: 'netflow.destination_ipv6_address', + type: 'ip', + }, + 'netflow.source_ipv6_prefix_length': { + category: 'netflow', + name: 'netflow.source_ipv6_prefix_length', + type: 'short', + }, + 'netflow.destination_ipv6_prefix_length': { + category: 'netflow', + name: 'netflow.destination_ipv6_prefix_length', + type: 'short', + }, + 'netflow.flow_label_ipv6': { + category: 'netflow', + name: 'netflow.flow_label_ipv6', + type: 'long', + }, + 'netflow.icmp_type_code_ipv4': { + category: 'netflow', + name: 'netflow.icmp_type_code_ipv4', + type: 'integer', + }, + 'netflow.igmp_type': { + category: 'netflow', + name: 'netflow.igmp_type', + type: 'short', + }, + 'netflow.sampling_interval': { + category: 'netflow', + name: 'netflow.sampling_interval', + type: 'long', + }, + 'netflow.sampling_algorithm': { + category: 'netflow', + name: 'netflow.sampling_algorithm', + type: 'short', + }, + 'netflow.flow_active_timeout': { + category: 'netflow', + name: 'netflow.flow_active_timeout', + type: 'integer', + }, + 'netflow.flow_idle_timeout': { + category: 'netflow', + name: 'netflow.flow_idle_timeout', + type: 'integer', + }, + 'netflow.engine_type': { + category: 'netflow', + name: 'netflow.engine_type', + type: 'short', + }, + 'netflow.engine_id': { + category: 'netflow', + name: 'netflow.engine_id', + type: 'short', + }, + 'netflow.exported_octet_total_count': { + category: 'netflow', + name: 'netflow.exported_octet_total_count', + type: 'long', + }, + 'netflow.exported_message_total_count': { + category: 'netflow', + name: 'netflow.exported_message_total_count', + type: 'long', + }, + 'netflow.exported_flow_record_total_count': { + category: 'netflow', + name: 'netflow.exported_flow_record_total_count', + type: 'long', + }, + 'netflow.ipv4_router_sc': { + category: 'netflow', + name: 'netflow.ipv4_router_sc', + type: 'ip', + }, + 'netflow.source_ipv4_prefix': { + category: 'netflow', + name: 'netflow.source_ipv4_prefix', + type: 'ip', + }, + 'netflow.destination_ipv4_prefix': { + category: 'netflow', + name: 'netflow.destination_ipv4_prefix', + type: 'ip', + }, + 'netflow.mpls_top_label_type': { + category: 'netflow', + name: 'netflow.mpls_top_label_type', + type: 'short', + }, + 'netflow.mpls_top_label_ipv4_address': { + category: 'netflow', + name: 'netflow.mpls_top_label_ipv4_address', + type: 'ip', + }, + 'netflow.sampler_id': { + category: 'netflow', + name: 'netflow.sampler_id', + type: 'short', + }, + 'netflow.sampler_mode': { + category: 'netflow', + name: 'netflow.sampler_mode', + type: 'short', + }, + 'netflow.sampler_random_interval': { + category: 'netflow', + name: 'netflow.sampler_random_interval', + type: 'long', + }, + 'netflow.class_id': { + category: 'netflow', + name: 'netflow.class_id', + type: 'long', + }, + 'netflow.minimum_ttl': { + category: 'netflow', + name: 'netflow.minimum_ttl', + type: 'short', + }, + 'netflow.maximum_ttl': { + category: 'netflow', + name: 'netflow.maximum_ttl', + type: 'short', + }, + 'netflow.fragment_identification': { + category: 'netflow', + name: 'netflow.fragment_identification', + type: 'long', + }, + 'netflow.post_ip_class_of_service': { + category: 'netflow', + name: 'netflow.post_ip_class_of_service', + type: 'short', + }, + 'netflow.source_mac_address': { + category: 'netflow', + name: 'netflow.source_mac_address', + type: 'keyword', + }, + 'netflow.post_destination_mac_address': { + category: 'netflow', + name: 'netflow.post_destination_mac_address', + type: 'keyword', + }, + 'netflow.vlan_id': { + category: 'netflow', + name: 'netflow.vlan_id', + type: 'integer', + }, + 'netflow.post_vlan_id': { + category: 'netflow', + name: 'netflow.post_vlan_id', + type: 'integer', + }, + 'netflow.ip_version': { + category: 'netflow', + name: 'netflow.ip_version', + type: 'short', + }, + 'netflow.flow_direction': { + category: 'netflow', + name: 'netflow.flow_direction', + type: 'short', + }, + 'netflow.ip_next_hop_ipv6_address': { + category: 'netflow', + name: 'netflow.ip_next_hop_ipv6_address', + type: 'ip', + }, + 'netflow.bgp_next_hop_ipv6_address': { + category: 'netflow', + name: 'netflow.bgp_next_hop_ipv6_address', + type: 'ip', + }, + 'netflow.ipv6_extension_headers': { + category: 'netflow', + name: 'netflow.ipv6_extension_headers', + type: 'long', + }, + 'netflow.mpls_top_label_stack_section': { + category: 'netflow', + name: 'netflow.mpls_top_label_stack_section', + type: 'short', + }, + 'netflow.mpls_label_stack_section2': { + category: 'netflow', + name: 'netflow.mpls_label_stack_section2', + type: 'short', + }, + 'netflow.mpls_label_stack_section3': { + category: 'netflow', + name: 'netflow.mpls_label_stack_section3', + type: 'short', + }, + 'netflow.mpls_label_stack_section4': { + category: 'netflow', + name: 'netflow.mpls_label_stack_section4', + type: 'short', + }, + 'netflow.mpls_label_stack_section5': { + category: 'netflow', + name: 'netflow.mpls_label_stack_section5', + type: 'short', + }, + 'netflow.mpls_label_stack_section6': { + category: 'netflow', + name: 'netflow.mpls_label_stack_section6', + type: 'short', + }, + 'netflow.mpls_label_stack_section7': { + category: 'netflow', + name: 'netflow.mpls_label_stack_section7', + type: 'short', + }, + 'netflow.mpls_label_stack_section8': { + category: 'netflow', + name: 'netflow.mpls_label_stack_section8', + type: 'short', + }, + 'netflow.mpls_label_stack_section9': { + category: 'netflow', + name: 'netflow.mpls_label_stack_section9', + type: 'short', + }, + 'netflow.mpls_label_stack_section10': { + category: 'netflow', + name: 'netflow.mpls_label_stack_section10', + type: 'short', + }, + 'netflow.destination_mac_address': { + category: 'netflow', + name: 'netflow.destination_mac_address', + type: 'keyword', + }, + 'netflow.post_source_mac_address': { + category: 'netflow', + name: 'netflow.post_source_mac_address', + type: 'keyword', + }, + 'netflow.interface_name': { + category: 'netflow', + name: 'netflow.interface_name', + type: 'keyword', + }, + 'netflow.interface_description': { + category: 'netflow', + name: 'netflow.interface_description', + type: 'keyword', + }, + 'netflow.sampler_name': { + category: 'netflow', + name: 'netflow.sampler_name', + type: 'keyword', + }, + 'netflow.octet_total_count': { + category: 'netflow', + name: 'netflow.octet_total_count', + type: 'long', + }, + 'netflow.packet_total_count': { + category: 'netflow', + name: 'netflow.packet_total_count', + type: 'long', + }, + 'netflow.flags_and_sampler_id': { + category: 'netflow', + name: 'netflow.flags_and_sampler_id', + type: 'long', + }, + 'netflow.fragment_offset': { + category: 'netflow', + name: 'netflow.fragment_offset', + type: 'integer', + }, + 'netflow.forwarding_status': { + category: 'netflow', + name: 'netflow.forwarding_status', + type: 'short', + }, + 'netflow.mpls_vpn_route_distinguisher': { + category: 'netflow', + name: 'netflow.mpls_vpn_route_distinguisher', + type: 'short', + }, + 'netflow.mpls_top_label_prefix_length': { + category: 'netflow', + name: 'netflow.mpls_top_label_prefix_length', + type: 'short', + }, + 'netflow.src_traffic_index': { + category: 'netflow', + name: 'netflow.src_traffic_index', + type: 'long', + }, + 'netflow.dst_traffic_index': { + category: 'netflow', + name: 'netflow.dst_traffic_index', + type: 'long', + }, + 'netflow.application_description': { + category: 'netflow', + name: 'netflow.application_description', + type: 'keyword', + }, + 'netflow.application_id': { + category: 'netflow', + name: 'netflow.application_id', + type: 'short', + }, + 'netflow.application_name': { + category: 'netflow', + name: 'netflow.application_name', + type: 'keyword', + }, + 'netflow.post_ip_diff_serv_code_point': { + category: 'netflow', + name: 'netflow.post_ip_diff_serv_code_point', + type: 'short', + }, + 'netflow.multicast_replication_factor': { + category: 'netflow', + name: 'netflow.multicast_replication_factor', + type: 'long', + }, + 'netflow.class_name': { + category: 'netflow', + name: 'netflow.class_name', + type: 'keyword', + }, + 'netflow.classification_engine_id': { + category: 'netflow', + name: 'netflow.classification_engine_id', + type: 'short', + }, + 'netflow.layer2packet_section_offset': { + category: 'netflow', + name: 'netflow.layer2packet_section_offset', + type: 'integer', + }, + 'netflow.layer2packet_section_size': { + category: 'netflow', + name: 'netflow.layer2packet_section_size', + type: 'integer', + }, + 'netflow.layer2packet_section_data': { + category: 'netflow', + name: 'netflow.layer2packet_section_data', + type: 'short', + }, + 'netflow.bgp_next_adjacent_as_number': { + category: 'netflow', + name: 'netflow.bgp_next_adjacent_as_number', + type: 'long', + }, + 'netflow.bgp_prev_adjacent_as_number': { + category: 'netflow', + name: 'netflow.bgp_prev_adjacent_as_number', + type: 'long', + }, + 'netflow.exporter_ipv4_address': { + category: 'netflow', + name: 'netflow.exporter_ipv4_address', + type: 'ip', + }, + 'netflow.exporter_ipv6_address': { + category: 'netflow', + name: 'netflow.exporter_ipv6_address', + type: 'ip', + }, + 'netflow.dropped_octet_delta_count': { + category: 'netflow', + name: 'netflow.dropped_octet_delta_count', + type: 'long', + }, + 'netflow.dropped_packet_delta_count': { + category: 'netflow', + name: 'netflow.dropped_packet_delta_count', + type: 'long', + }, + 'netflow.dropped_octet_total_count': { + category: 'netflow', + name: 'netflow.dropped_octet_total_count', + type: 'long', + }, + 'netflow.dropped_packet_total_count': { + category: 'netflow', + name: 'netflow.dropped_packet_total_count', + type: 'long', + }, + 'netflow.flow_end_reason': { + category: 'netflow', + name: 'netflow.flow_end_reason', + type: 'short', + }, + 'netflow.common_properties_id': { + category: 'netflow', + name: 'netflow.common_properties_id', + type: 'long', + }, + 'netflow.observation_point_id': { + category: 'netflow', + name: 'netflow.observation_point_id', + type: 'long', + }, + 'netflow.icmp_type_code_ipv6': { + category: 'netflow', + name: 'netflow.icmp_type_code_ipv6', + type: 'integer', + }, + 'netflow.mpls_top_label_ipv6_address': { + category: 'netflow', + name: 'netflow.mpls_top_label_ipv6_address', + type: 'ip', + }, + 'netflow.line_card_id': { + category: 'netflow', + name: 'netflow.line_card_id', + type: 'long', + }, + 'netflow.port_id': { + category: 'netflow', + name: 'netflow.port_id', + type: 'long', + }, + 'netflow.metering_process_id': { + category: 'netflow', + name: 'netflow.metering_process_id', + type: 'long', + }, + 'netflow.exporting_process_id': { + category: 'netflow', + name: 'netflow.exporting_process_id', + type: 'long', + }, + 'netflow.template_id': { + category: 'netflow', + name: 'netflow.template_id', + type: 'integer', + }, + 'netflow.wlan_channel_id': { + category: 'netflow', + name: 'netflow.wlan_channel_id', + type: 'short', + }, + 'netflow.wlan_ssid': { + category: 'netflow', + name: 'netflow.wlan_ssid', + type: 'keyword', + }, + 'netflow.flow_id': { + category: 'netflow', + name: 'netflow.flow_id', + type: 'long', + }, + 'netflow.observation_domain_id': { + category: 'netflow', + name: 'netflow.observation_domain_id', + type: 'long', + }, + 'netflow.flow_start_seconds': { + category: 'netflow', + name: 'netflow.flow_start_seconds', + type: 'date', + }, + 'netflow.flow_end_seconds': { + category: 'netflow', + name: 'netflow.flow_end_seconds', + type: 'date', + }, + 'netflow.flow_start_milliseconds': { + category: 'netflow', + name: 'netflow.flow_start_milliseconds', + type: 'date', + }, + 'netflow.flow_end_milliseconds': { + category: 'netflow', + name: 'netflow.flow_end_milliseconds', + type: 'date', + }, + 'netflow.flow_start_microseconds': { + category: 'netflow', + name: 'netflow.flow_start_microseconds', + type: 'date', + }, + 'netflow.flow_end_microseconds': { + category: 'netflow', + name: 'netflow.flow_end_microseconds', + type: 'date', + }, + 'netflow.flow_start_nanoseconds': { + category: 'netflow', + name: 'netflow.flow_start_nanoseconds', + type: 'date', + }, + 'netflow.flow_end_nanoseconds': { + category: 'netflow', + name: 'netflow.flow_end_nanoseconds', + type: 'date', + }, + 'netflow.flow_start_delta_microseconds': { + category: 'netflow', + name: 'netflow.flow_start_delta_microseconds', + type: 'long', + }, + 'netflow.flow_end_delta_microseconds': { + category: 'netflow', + name: 'netflow.flow_end_delta_microseconds', + type: 'long', + }, + 'netflow.system_init_time_milliseconds': { + category: 'netflow', + name: 'netflow.system_init_time_milliseconds', + type: 'date', + }, + 'netflow.flow_duration_milliseconds': { + category: 'netflow', + name: 'netflow.flow_duration_milliseconds', + type: 'long', + }, + 'netflow.flow_duration_microseconds': { + category: 'netflow', + name: 'netflow.flow_duration_microseconds', + type: 'long', + }, + 'netflow.observed_flow_total_count': { + category: 'netflow', + name: 'netflow.observed_flow_total_count', + type: 'long', + }, + 'netflow.ignored_packet_total_count': { + category: 'netflow', + name: 'netflow.ignored_packet_total_count', + type: 'long', + }, + 'netflow.ignored_octet_total_count': { + category: 'netflow', + name: 'netflow.ignored_octet_total_count', + type: 'long', + }, + 'netflow.not_sent_flow_total_count': { + category: 'netflow', + name: 'netflow.not_sent_flow_total_count', + type: 'long', + }, + 'netflow.not_sent_packet_total_count': { + category: 'netflow', + name: 'netflow.not_sent_packet_total_count', + type: 'long', + }, + 'netflow.not_sent_octet_total_count': { + category: 'netflow', + name: 'netflow.not_sent_octet_total_count', + type: 'long', + }, + 'netflow.destination_ipv6_prefix': { + category: 'netflow', + name: 'netflow.destination_ipv6_prefix', + type: 'ip', + }, + 'netflow.source_ipv6_prefix': { + category: 'netflow', + name: 'netflow.source_ipv6_prefix', + type: 'ip', + }, + 'netflow.post_octet_total_count': { + category: 'netflow', + name: 'netflow.post_octet_total_count', + type: 'long', + }, + 'netflow.post_packet_total_count': { + category: 'netflow', + name: 'netflow.post_packet_total_count', + type: 'long', + }, + 'netflow.flow_key_indicator': { + category: 'netflow', + name: 'netflow.flow_key_indicator', + type: 'long', + }, + 'netflow.post_mcast_packet_total_count': { + category: 'netflow', + name: 'netflow.post_mcast_packet_total_count', + type: 'long', + }, + 'netflow.post_mcast_octet_total_count': { + category: 'netflow', + name: 'netflow.post_mcast_octet_total_count', + type: 'long', + }, + 'netflow.icmp_type_ipv4': { + category: 'netflow', + name: 'netflow.icmp_type_ipv4', + type: 'short', + }, + 'netflow.icmp_code_ipv4': { + category: 'netflow', + name: 'netflow.icmp_code_ipv4', + type: 'short', + }, + 'netflow.icmp_type_ipv6': { + category: 'netflow', + name: 'netflow.icmp_type_ipv6', + type: 'short', + }, + 'netflow.icmp_code_ipv6': { + category: 'netflow', + name: 'netflow.icmp_code_ipv6', + type: 'short', + }, + 'netflow.udp_source_port': { + category: 'netflow', + name: 'netflow.udp_source_port', + type: 'integer', + }, + 'netflow.udp_destination_port': { + category: 'netflow', + name: 'netflow.udp_destination_port', + type: 'integer', + }, + 'netflow.tcp_source_port': { + category: 'netflow', + name: 'netflow.tcp_source_port', + type: 'integer', + }, + 'netflow.tcp_destination_port': { + category: 'netflow', + name: 'netflow.tcp_destination_port', + type: 'integer', + }, + 'netflow.tcp_sequence_number': { + category: 'netflow', + name: 'netflow.tcp_sequence_number', + type: 'long', + }, + 'netflow.tcp_acknowledgement_number': { + category: 'netflow', + name: 'netflow.tcp_acknowledgement_number', + type: 'long', + }, + 'netflow.tcp_window_size': { + category: 'netflow', + name: 'netflow.tcp_window_size', + type: 'integer', + }, + 'netflow.tcp_urgent_pointer': { + category: 'netflow', + name: 'netflow.tcp_urgent_pointer', + type: 'integer', + }, + 'netflow.tcp_header_length': { + category: 'netflow', + name: 'netflow.tcp_header_length', + type: 'short', + }, + 'netflow.ip_header_length': { + category: 'netflow', + name: 'netflow.ip_header_length', + type: 'short', + }, + 'netflow.total_length_ipv4': { + category: 'netflow', + name: 'netflow.total_length_ipv4', + type: 'integer', + }, + 'netflow.payload_length_ipv6': { + category: 'netflow', + name: 'netflow.payload_length_ipv6', + type: 'integer', + }, + 'netflow.ip_ttl': { + category: 'netflow', + name: 'netflow.ip_ttl', + type: 'short', + }, + 'netflow.next_header_ipv6': { + category: 'netflow', + name: 'netflow.next_header_ipv6', + type: 'short', + }, + 'netflow.mpls_payload_length': { + category: 'netflow', + name: 'netflow.mpls_payload_length', + type: 'long', + }, + 'netflow.ip_diff_serv_code_point': { + category: 'netflow', + name: 'netflow.ip_diff_serv_code_point', + type: 'short', + }, + 'netflow.ip_precedence': { + category: 'netflow', + name: 'netflow.ip_precedence', + type: 'short', + }, + 'netflow.fragment_flags': { + category: 'netflow', + name: 'netflow.fragment_flags', + type: 'short', + }, + 'netflow.octet_delta_sum_of_squares': { + category: 'netflow', + name: 'netflow.octet_delta_sum_of_squares', + type: 'long', + }, + 'netflow.octet_total_sum_of_squares': { + category: 'netflow', + name: 'netflow.octet_total_sum_of_squares', + type: 'long', + }, + 'netflow.mpls_top_label_ttl': { + category: 'netflow', + name: 'netflow.mpls_top_label_ttl', + type: 'short', + }, + 'netflow.mpls_label_stack_length': { + category: 'netflow', + name: 'netflow.mpls_label_stack_length', + type: 'long', + }, + 'netflow.mpls_label_stack_depth': { + category: 'netflow', + name: 'netflow.mpls_label_stack_depth', + type: 'long', + }, + 'netflow.mpls_top_label_exp': { + category: 'netflow', + name: 'netflow.mpls_top_label_exp', + type: 'short', + }, + 'netflow.ip_payload_length': { + category: 'netflow', + name: 'netflow.ip_payload_length', + type: 'long', + }, + 'netflow.udp_message_length': { + category: 'netflow', + name: 'netflow.udp_message_length', + type: 'integer', + }, + 'netflow.is_multicast': { + category: 'netflow', + name: 'netflow.is_multicast', + type: 'short', + }, + 'netflow.ipv4_ihl': { + category: 'netflow', + name: 'netflow.ipv4_ihl', + type: 'short', + }, + 'netflow.ipv4_options': { + category: 'netflow', + name: 'netflow.ipv4_options', + type: 'long', + }, + 'netflow.tcp_options': { + category: 'netflow', + name: 'netflow.tcp_options', + type: 'long', + }, + 'netflow.padding_octets': { + category: 'netflow', + name: 'netflow.padding_octets', + type: 'short', + }, + 'netflow.collector_ipv4_address': { + category: 'netflow', + name: 'netflow.collector_ipv4_address', + type: 'ip', + }, + 'netflow.collector_ipv6_address': { + category: 'netflow', + name: 'netflow.collector_ipv6_address', + type: 'ip', + }, + 'netflow.export_interface': { + category: 'netflow', + name: 'netflow.export_interface', + type: 'long', + }, + 'netflow.export_protocol_version': { + category: 'netflow', + name: 'netflow.export_protocol_version', + type: 'short', + }, + 'netflow.export_transport_protocol': { + category: 'netflow', + name: 'netflow.export_transport_protocol', + type: 'short', + }, + 'netflow.collector_transport_port': { + category: 'netflow', + name: 'netflow.collector_transport_port', + type: 'integer', + }, + 'netflow.exporter_transport_port': { + category: 'netflow', + name: 'netflow.exporter_transport_port', + type: 'integer', + }, + 'netflow.tcp_syn_total_count': { + category: 'netflow', + name: 'netflow.tcp_syn_total_count', + type: 'long', + }, + 'netflow.tcp_fin_total_count': { + category: 'netflow', + name: 'netflow.tcp_fin_total_count', + type: 'long', + }, + 'netflow.tcp_rst_total_count': { + category: 'netflow', + name: 'netflow.tcp_rst_total_count', + type: 'long', + }, + 'netflow.tcp_psh_total_count': { + category: 'netflow', + name: 'netflow.tcp_psh_total_count', + type: 'long', + }, + 'netflow.tcp_ack_total_count': { + category: 'netflow', + name: 'netflow.tcp_ack_total_count', + type: 'long', + }, + 'netflow.tcp_urg_total_count': { + category: 'netflow', + name: 'netflow.tcp_urg_total_count', + type: 'long', + }, + 'netflow.ip_total_length': { + category: 'netflow', + name: 'netflow.ip_total_length', + type: 'long', + }, + 'netflow.post_nat_source_ipv4_address': { + category: 'netflow', + name: 'netflow.post_nat_source_ipv4_address', + type: 'ip', + }, + 'netflow.post_nat_destination_ipv4_address': { + category: 'netflow', + name: 'netflow.post_nat_destination_ipv4_address', + type: 'ip', + }, + 'netflow.post_napt_source_transport_port': { + category: 'netflow', + name: 'netflow.post_napt_source_transport_port', + type: 'integer', + }, + 'netflow.post_napt_destination_transport_port': { + category: 'netflow', + name: 'netflow.post_napt_destination_transport_port', + type: 'integer', + }, + 'netflow.nat_originating_address_realm': { + category: 'netflow', + name: 'netflow.nat_originating_address_realm', + type: 'short', + }, + 'netflow.nat_event': { + category: 'netflow', + name: 'netflow.nat_event', + type: 'short', + }, + 'netflow.initiator_octets': { + category: 'netflow', + name: 'netflow.initiator_octets', + type: 'long', + }, + 'netflow.responder_octets': { + category: 'netflow', + name: 'netflow.responder_octets', + type: 'long', + }, + 'netflow.firewall_event': { + category: 'netflow', + name: 'netflow.firewall_event', + type: 'short', + }, + 'netflow.ingress_vrfid': { + category: 'netflow', + name: 'netflow.ingress_vrfid', + type: 'long', + }, + 'netflow.egress_vrfid': { + category: 'netflow', + name: 'netflow.egress_vrfid', + type: 'long', + }, + 'netflow.vr_fname': { + category: 'netflow', + name: 'netflow.vr_fname', + type: 'keyword', + }, + 'netflow.post_mpls_top_label_exp': { + category: 'netflow', + name: 'netflow.post_mpls_top_label_exp', + type: 'short', + }, + 'netflow.tcp_window_scale': { + category: 'netflow', + name: 'netflow.tcp_window_scale', + type: 'integer', + }, + 'netflow.biflow_direction': { + category: 'netflow', + name: 'netflow.biflow_direction', + type: 'short', + }, + 'netflow.ethernet_header_length': { + category: 'netflow', + name: 'netflow.ethernet_header_length', + type: 'short', + }, + 'netflow.ethernet_payload_length': { + category: 'netflow', + name: 'netflow.ethernet_payload_length', + type: 'integer', + }, + 'netflow.ethernet_total_length': { + category: 'netflow', + name: 'netflow.ethernet_total_length', + type: 'integer', + }, + 'netflow.dot1q_vlan_id': { + category: 'netflow', + name: 'netflow.dot1q_vlan_id', + type: 'integer', + }, + 'netflow.dot1q_priority': { + category: 'netflow', + name: 'netflow.dot1q_priority', + type: 'short', + }, + 'netflow.dot1q_customer_vlan_id': { + category: 'netflow', + name: 'netflow.dot1q_customer_vlan_id', + type: 'integer', + }, + 'netflow.dot1q_customer_priority': { + category: 'netflow', + name: 'netflow.dot1q_customer_priority', + type: 'short', + }, + 'netflow.metro_evc_id': { + category: 'netflow', + name: 'netflow.metro_evc_id', + type: 'keyword', + }, + 'netflow.metro_evc_type': { + category: 'netflow', + name: 'netflow.metro_evc_type', + type: 'short', + }, + 'netflow.pseudo_wire_id': { + category: 'netflow', + name: 'netflow.pseudo_wire_id', + type: 'long', + }, + 'netflow.pseudo_wire_type': { + category: 'netflow', + name: 'netflow.pseudo_wire_type', + type: 'integer', + }, + 'netflow.pseudo_wire_control_word': { + category: 'netflow', + name: 'netflow.pseudo_wire_control_word', + type: 'long', + }, + 'netflow.ingress_physical_interface': { + category: 'netflow', + name: 'netflow.ingress_physical_interface', + type: 'long', + }, + 'netflow.egress_physical_interface': { + category: 'netflow', + name: 'netflow.egress_physical_interface', + type: 'long', + }, + 'netflow.post_dot1q_vlan_id': { + category: 'netflow', + name: 'netflow.post_dot1q_vlan_id', + type: 'integer', + }, + 'netflow.post_dot1q_customer_vlan_id': { + category: 'netflow', + name: 'netflow.post_dot1q_customer_vlan_id', + type: 'integer', + }, + 'netflow.ethernet_type': { + category: 'netflow', + name: 'netflow.ethernet_type', + type: 'integer', + }, + 'netflow.post_ip_precedence': { + category: 'netflow', + name: 'netflow.post_ip_precedence', + type: 'short', + }, + 'netflow.collection_time_milliseconds': { + category: 'netflow', + name: 'netflow.collection_time_milliseconds', + type: 'date', + }, + 'netflow.export_sctp_stream_id': { + category: 'netflow', + name: 'netflow.export_sctp_stream_id', + type: 'integer', + }, + 'netflow.max_export_seconds': { + category: 'netflow', + name: 'netflow.max_export_seconds', + type: 'date', + }, + 'netflow.max_flow_end_seconds': { + category: 'netflow', + name: 'netflow.max_flow_end_seconds', + type: 'date', + }, + 'netflow.message_md5_checksum': { + category: 'netflow', + name: 'netflow.message_md5_checksum', + type: 'short', + }, + 'netflow.message_scope': { + category: 'netflow', + name: 'netflow.message_scope', + type: 'short', + }, + 'netflow.min_export_seconds': { + category: 'netflow', + name: 'netflow.min_export_seconds', + type: 'date', + }, + 'netflow.min_flow_start_seconds': { + category: 'netflow', + name: 'netflow.min_flow_start_seconds', + type: 'date', + }, + 'netflow.opaque_octets': { + category: 'netflow', + name: 'netflow.opaque_octets', + type: 'short', + }, + 'netflow.session_scope': { + category: 'netflow', + name: 'netflow.session_scope', + type: 'short', + }, + 'netflow.max_flow_end_microseconds': { + category: 'netflow', + name: 'netflow.max_flow_end_microseconds', + type: 'date', + }, + 'netflow.max_flow_end_milliseconds': { + category: 'netflow', + name: 'netflow.max_flow_end_milliseconds', + type: 'date', + }, + 'netflow.max_flow_end_nanoseconds': { + category: 'netflow', + name: 'netflow.max_flow_end_nanoseconds', + type: 'date', + }, + 'netflow.min_flow_start_microseconds': { + category: 'netflow', + name: 'netflow.min_flow_start_microseconds', + type: 'date', + }, + 'netflow.min_flow_start_milliseconds': { + category: 'netflow', + name: 'netflow.min_flow_start_milliseconds', + type: 'date', + }, + 'netflow.min_flow_start_nanoseconds': { + category: 'netflow', + name: 'netflow.min_flow_start_nanoseconds', + type: 'date', + }, + 'netflow.collector_certificate': { + category: 'netflow', + name: 'netflow.collector_certificate', + type: 'short', + }, + 'netflow.exporter_certificate': { + category: 'netflow', + name: 'netflow.exporter_certificate', + type: 'short', + }, + 'netflow.data_records_reliability': { + category: 'netflow', + name: 'netflow.data_records_reliability', + type: 'boolean', + }, + 'netflow.observation_point_type': { + category: 'netflow', + name: 'netflow.observation_point_type', + type: 'short', + }, + 'netflow.new_connection_delta_count': { + category: 'netflow', + name: 'netflow.new_connection_delta_count', + type: 'long', + }, + 'netflow.connection_sum_duration_seconds': { + category: 'netflow', + name: 'netflow.connection_sum_duration_seconds', + type: 'long', + }, + 'netflow.connection_transaction_id': { + category: 'netflow', + name: 'netflow.connection_transaction_id', + type: 'long', + }, + 'netflow.post_nat_source_ipv6_address': { + category: 'netflow', + name: 'netflow.post_nat_source_ipv6_address', + type: 'ip', + }, + 'netflow.post_nat_destination_ipv6_address': { + category: 'netflow', + name: 'netflow.post_nat_destination_ipv6_address', + type: 'ip', + }, + 'netflow.nat_pool_id': { + category: 'netflow', + name: 'netflow.nat_pool_id', + type: 'long', + }, + 'netflow.nat_pool_name': { + category: 'netflow', + name: 'netflow.nat_pool_name', + type: 'keyword', + }, + 'netflow.anonymization_flags': { + category: 'netflow', + name: 'netflow.anonymization_flags', + type: 'integer', + }, + 'netflow.anonymization_technique': { + category: 'netflow', + name: 'netflow.anonymization_technique', + type: 'integer', + }, + 'netflow.information_element_index': { + category: 'netflow', + name: 'netflow.information_element_index', + type: 'integer', + }, + 'netflow.p2p_technology': { + category: 'netflow', + name: 'netflow.p2p_technology', + type: 'keyword', + }, + 'netflow.tunnel_technology': { + category: 'netflow', + name: 'netflow.tunnel_technology', + type: 'keyword', + }, + 'netflow.encrypted_technology': { + category: 'netflow', + name: 'netflow.encrypted_technology', + type: 'keyword', + }, + 'netflow.bgp_validity_state': { + category: 'netflow', + name: 'netflow.bgp_validity_state', + type: 'short', + }, + 'netflow.ip_sec_spi': { + category: 'netflow', + name: 'netflow.ip_sec_spi', + type: 'long', + }, + 'netflow.gre_key': { + category: 'netflow', + name: 'netflow.gre_key', + type: 'long', + }, + 'netflow.nat_type': { + category: 'netflow', + name: 'netflow.nat_type', + type: 'short', + }, + 'netflow.initiator_packets': { + category: 'netflow', + name: 'netflow.initiator_packets', + type: 'long', + }, + 'netflow.responder_packets': { + category: 'netflow', + name: 'netflow.responder_packets', + type: 'long', + }, + 'netflow.observation_domain_name': { + category: 'netflow', + name: 'netflow.observation_domain_name', + type: 'keyword', + }, + 'netflow.selection_sequence_id': { + category: 'netflow', + name: 'netflow.selection_sequence_id', + type: 'long', + }, + 'netflow.selector_id': { + category: 'netflow', + name: 'netflow.selector_id', + type: 'long', + }, + 'netflow.information_element_id': { + category: 'netflow', + name: 'netflow.information_element_id', + type: 'integer', + }, + 'netflow.selector_algorithm': { + category: 'netflow', + name: 'netflow.selector_algorithm', + type: 'integer', + }, + 'netflow.sampling_packet_interval': { + category: 'netflow', + name: 'netflow.sampling_packet_interval', + type: 'long', + }, + 'netflow.sampling_packet_space': { + category: 'netflow', + name: 'netflow.sampling_packet_space', + type: 'long', + }, + 'netflow.sampling_time_interval': { + category: 'netflow', + name: 'netflow.sampling_time_interval', + type: 'long', + }, + 'netflow.sampling_time_space': { + category: 'netflow', + name: 'netflow.sampling_time_space', + type: 'long', + }, + 'netflow.sampling_size': { + category: 'netflow', + name: 'netflow.sampling_size', + type: 'long', + }, + 'netflow.sampling_population': { + category: 'netflow', + name: 'netflow.sampling_population', + type: 'long', + }, + 'netflow.sampling_probability': { + category: 'netflow', + name: 'netflow.sampling_probability', + type: 'double', + }, + 'netflow.data_link_frame_size': { + category: 'netflow', + name: 'netflow.data_link_frame_size', + type: 'integer', + }, + 'netflow.ip_header_packet_section': { + category: 'netflow', + name: 'netflow.ip_header_packet_section', + type: 'short', + }, + 'netflow.ip_payload_packet_section': { + category: 'netflow', + name: 'netflow.ip_payload_packet_section', + type: 'short', + }, + 'netflow.data_link_frame_section': { + category: 'netflow', + name: 'netflow.data_link_frame_section', + type: 'short', + }, + 'netflow.mpls_label_stack_section': { + category: 'netflow', + name: 'netflow.mpls_label_stack_section', + type: 'short', + }, + 'netflow.mpls_payload_packet_section': { + category: 'netflow', + name: 'netflow.mpls_payload_packet_section', + type: 'short', + }, + 'netflow.selector_id_total_pkts_observed': { + category: 'netflow', + name: 'netflow.selector_id_total_pkts_observed', + type: 'long', + }, + 'netflow.selector_id_total_pkts_selected': { + category: 'netflow', + name: 'netflow.selector_id_total_pkts_selected', + type: 'long', + }, + 'netflow.absolute_error': { + category: 'netflow', + name: 'netflow.absolute_error', + type: 'double', + }, + 'netflow.relative_error': { + category: 'netflow', + name: 'netflow.relative_error', + type: 'double', + }, + 'netflow.observation_time_seconds': { + category: 'netflow', + name: 'netflow.observation_time_seconds', + type: 'date', + }, + 'netflow.observation_time_milliseconds': { + category: 'netflow', + name: 'netflow.observation_time_milliseconds', + type: 'date', + }, + 'netflow.observation_time_microseconds': { + category: 'netflow', + name: 'netflow.observation_time_microseconds', + type: 'date', + }, + 'netflow.observation_time_nanoseconds': { + category: 'netflow', + name: 'netflow.observation_time_nanoseconds', + type: 'date', + }, + 'netflow.digest_hash_value': { + category: 'netflow', + name: 'netflow.digest_hash_value', + type: 'long', + }, + 'netflow.hash_ip_payload_offset': { + category: 'netflow', + name: 'netflow.hash_ip_payload_offset', + type: 'long', + }, + 'netflow.hash_ip_payload_size': { + category: 'netflow', + name: 'netflow.hash_ip_payload_size', + type: 'long', + }, + 'netflow.hash_output_range_min': { + category: 'netflow', + name: 'netflow.hash_output_range_min', + type: 'long', + }, + 'netflow.hash_output_range_max': { + category: 'netflow', + name: 'netflow.hash_output_range_max', + type: 'long', + }, + 'netflow.hash_selected_range_min': { + category: 'netflow', + name: 'netflow.hash_selected_range_min', + type: 'long', + }, + 'netflow.hash_selected_range_max': { + category: 'netflow', + name: 'netflow.hash_selected_range_max', + type: 'long', + }, + 'netflow.hash_digest_output': { + category: 'netflow', + name: 'netflow.hash_digest_output', + type: 'boolean', + }, + 'netflow.hash_initialiser_value': { + category: 'netflow', + name: 'netflow.hash_initialiser_value', + type: 'long', + }, + 'netflow.selector_name': { + category: 'netflow', + name: 'netflow.selector_name', + type: 'keyword', + }, + 'netflow.upper_ci_limit': { + category: 'netflow', + name: 'netflow.upper_ci_limit', + type: 'double', + }, + 'netflow.lower_ci_limit': { + category: 'netflow', + name: 'netflow.lower_ci_limit', + type: 'double', + }, + 'netflow.confidence_level': { + category: 'netflow', + name: 'netflow.confidence_level', + type: 'double', + }, + 'netflow.information_element_data_type': { + category: 'netflow', + name: 'netflow.information_element_data_type', + type: 'short', + }, + 'netflow.information_element_description': { + category: 'netflow', + name: 'netflow.information_element_description', + type: 'keyword', + }, + 'netflow.information_element_name': { + category: 'netflow', + name: 'netflow.information_element_name', + type: 'keyword', + }, + 'netflow.information_element_range_begin': { + category: 'netflow', + name: 'netflow.information_element_range_begin', + type: 'long', + }, + 'netflow.information_element_range_end': { + category: 'netflow', + name: 'netflow.information_element_range_end', + type: 'long', + }, + 'netflow.information_element_semantics': { + category: 'netflow', + name: 'netflow.information_element_semantics', + type: 'short', + }, + 'netflow.information_element_units': { + category: 'netflow', + name: 'netflow.information_element_units', + type: 'integer', + }, + 'netflow.private_enterprise_number': { + category: 'netflow', + name: 'netflow.private_enterprise_number', + type: 'long', + }, + 'netflow.virtual_station_interface_id': { + category: 'netflow', + name: 'netflow.virtual_station_interface_id', + type: 'short', + }, + 'netflow.virtual_station_interface_name': { + category: 'netflow', + name: 'netflow.virtual_station_interface_name', + type: 'keyword', + }, + 'netflow.virtual_station_uuid': { + category: 'netflow', + name: 'netflow.virtual_station_uuid', + type: 'short', + }, + 'netflow.virtual_station_name': { + category: 'netflow', + name: 'netflow.virtual_station_name', + type: 'keyword', + }, + 'netflow.layer2_segment_id': { + category: 'netflow', + name: 'netflow.layer2_segment_id', + type: 'long', + }, + 'netflow.layer2_octet_delta_count': { + category: 'netflow', + name: 'netflow.layer2_octet_delta_count', + type: 'long', + }, + 'netflow.layer2_octet_total_count': { + category: 'netflow', + name: 'netflow.layer2_octet_total_count', + type: 'long', + }, + 'netflow.ingress_unicast_packet_total_count': { + category: 'netflow', + name: 'netflow.ingress_unicast_packet_total_count', + type: 'long', + }, + 'netflow.ingress_multicast_packet_total_count': { + category: 'netflow', + name: 'netflow.ingress_multicast_packet_total_count', + type: 'long', + }, + 'netflow.ingress_broadcast_packet_total_count': { + category: 'netflow', + name: 'netflow.ingress_broadcast_packet_total_count', + type: 'long', + }, + 'netflow.egress_unicast_packet_total_count': { + category: 'netflow', + name: 'netflow.egress_unicast_packet_total_count', + type: 'long', + }, + 'netflow.egress_broadcast_packet_total_count': { + category: 'netflow', + name: 'netflow.egress_broadcast_packet_total_count', + type: 'long', + }, + 'netflow.monitoring_interval_start_milli_seconds': { + category: 'netflow', + name: 'netflow.monitoring_interval_start_milli_seconds', + type: 'date', + }, + 'netflow.monitoring_interval_end_milli_seconds': { + category: 'netflow', + name: 'netflow.monitoring_interval_end_milli_seconds', + type: 'date', + }, + 'netflow.port_range_start': { + category: 'netflow', + name: 'netflow.port_range_start', + type: 'integer', + }, + 'netflow.port_range_end': { + category: 'netflow', + name: 'netflow.port_range_end', + type: 'integer', + }, + 'netflow.port_range_step_size': { + category: 'netflow', + name: 'netflow.port_range_step_size', + type: 'integer', + }, + 'netflow.port_range_num_ports': { + category: 'netflow', + name: 'netflow.port_range_num_ports', + type: 'integer', + }, + 'netflow.sta_mac_address': { + category: 'netflow', + name: 'netflow.sta_mac_address', + type: 'keyword', + }, + 'netflow.sta_ipv4_address': { + category: 'netflow', + name: 'netflow.sta_ipv4_address', + type: 'ip', + }, + 'netflow.wtp_mac_address': { + category: 'netflow', + name: 'netflow.wtp_mac_address', + type: 'keyword', + }, + 'netflow.ingress_interface_type': { + category: 'netflow', + name: 'netflow.ingress_interface_type', + type: 'long', + }, + 'netflow.egress_interface_type': { + category: 'netflow', + name: 'netflow.egress_interface_type', + type: 'long', + }, + 'netflow.rtp_sequence_number': { + category: 'netflow', + name: 'netflow.rtp_sequence_number', + type: 'integer', + }, + 'netflow.user_name': { + category: 'netflow', + name: 'netflow.user_name', + type: 'keyword', + }, + 'netflow.application_category_name': { + category: 'netflow', + name: 'netflow.application_category_name', + type: 'keyword', + }, + 'netflow.application_sub_category_name': { + category: 'netflow', + name: 'netflow.application_sub_category_name', + type: 'keyword', + }, + 'netflow.application_group_name': { + category: 'netflow', + name: 'netflow.application_group_name', + type: 'keyword', + }, + 'netflow.original_flows_present': { + category: 'netflow', + name: 'netflow.original_flows_present', + type: 'long', + }, + 'netflow.original_flows_initiated': { + category: 'netflow', + name: 'netflow.original_flows_initiated', + type: 'long', + }, + 'netflow.original_flows_completed': { + category: 'netflow', + name: 'netflow.original_flows_completed', + type: 'long', + }, + 'netflow.distinct_count_of_source_ip_address': { + category: 'netflow', + name: 'netflow.distinct_count_of_source_ip_address', + type: 'long', + }, + 'netflow.distinct_count_of_destination_ip_address': { + category: 'netflow', + name: 'netflow.distinct_count_of_destination_ip_address', + type: 'long', + }, + 'netflow.distinct_count_of_source_ipv4_address': { + category: 'netflow', + name: 'netflow.distinct_count_of_source_ipv4_address', + type: 'long', + }, + 'netflow.distinct_count_of_destination_ipv4_address': { + category: 'netflow', + name: 'netflow.distinct_count_of_destination_ipv4_address', + type: 'long', + }, + 'netflow.distinct_count_of_source_ipv6_address': { + category: 'netflow', + name: 'netflow.distinct_count_of_source_ipv6_address', + type: 'long', + }, + 'netflow.distinct_count_of_destination_ipv6_address': { + category: 'netflow', + name: 'netflow.distinct_count_of_destination_ipv6_address', + type: 'long', + }, + 'netflow.value_distribution_method': { + category: 'netflow', + name: 'netflow.value_distribution_method', + type: 'short', + }, + 'netflow.rfc3550_jitter_milliseconds': { + category: 'netflow', + name: 'netflow.rfc3550_jitter_milliseconds', + type: 'long', + }, + 'netflow.rfc3550_jitter_microseconds': { + category: 'netflow', + name: 'netflow.rfc3550_jitter_microseconds', + type: 'long', + }, + 'netflow.rfc3550_jitter_nanoseconds': { + category: 'netflow', + name: 'netflow.rfc3550_jitter_nanoseconds', + type: 'long', + }, + 'netflow.dot1q_dei': { + category: 'netflow', + name: 'netflow.dot1q_dei', + type: 'boolean', + }, + 'netflow.dot1q_customer_dei': { + category: 'netflow', + name: 'netflow.dot1q_customer_dei', + type: 'boolean', + }, + 'netflow.flow_selector_algorithm': { + category: 'netflow', + name: 'netflow.flow_selector_algorithm', + type: 'integer', + }, + 'netflow.flow_selected_octet_delta_count': { + category: 'netflow', + name: 'netflow.flow_selected_octet_delta_count', + type: 'long', + }, + 'netflow.flow_selected_packet_delta_count': { + category: 'netflow', + name: 'netflow.flow_selected_packet_delta_count', + type: 'long', + }, + 'netflow.flow_selected_flow_delta_count': { + category: 'netflow', + name: 'netflow.flow_selected_flow_delta_count', + type: 'long', + }, + 'netflow.selector_id_total_flows_observed': { + category: 'netflow', + name: 'netflow.selector_id_total_flows_observed', + type: 'long', + }, + 'netflow.selector_id_total_flows_selected': { + category: 'netflow', + name: 'netflow.selector_id_total_flows_selected', + type: 'long', + }, + 'netflow.sampling_flow_interval': { + category: 'netflow', + name: 'netflow.sampling_flow_interval', + type: 'long', + }, + 'netflow.sampling_flow_spacing': { + category: 'netflow', + name: 'netflow.sampling_flow_spacing', + type: 'long', + }, + 'netflow.flow_sampling_time_interval': { + category: 'netflow', + name: 'netflow.flow_sampling_time_interval', + type: 'long', + }, + 'netflow.flow_sampling_time_spacing': { + category: 'netflow', + name: 'netflow.flow_sampling_time_spacing', + type: 'long', + }, + 'netflow.hash_flow_domain': { + category: 'netflow', + name: 'netflow.hash_flow_domain', + type: 'integer', + }, + 'netflow.transport_octet_delta_count': { + category: 'netflow', + name: 'netflow.transport_octet_delta_count', + type: 'long', + }, + 'netflow.transport_packet_delta_count': { + category: 'netflow', + name: 'netflow.transport_packet_delta_count', + type: 'long', + }, + 'netflow.original_exporter_ipv4_address': { + category: 'netflow', + name: 'netflow.original_exporter_ipv4_address', + type: 'ip', + }, + 'netflow.original_exporter_ipv6_address': { + category: 'netflow', + name: 'netflow.original_exporter_ipv6_address', + type: 'ip', + }, + 'netflow.original_observation_domain_id': { + category: 'netflow', + name: 'netflow.original_observation_domain_id', + type: 'long', + }, + 'netflow.intermediate_process_id': { + category: 'netflow', + name: 'netflow.intermediate_process_id', + type: 'long', + }, + 'netflow.ignored_data_record_total_count': { + category: 'netflow', + name: 'netflow.ignored_data_record_total_count', + type: 'long', + }, + 'netflow.data_link_frame_type': { + category: 'netflow', + name: 'netflow.data_link_frame_type', + type: 'integer', + }, + 'netflow.section_offset': { + category: 'netflow', + name: 'netflow.section_offset', + type: 'integer', + }, + 'netflow.section_exported_octets': { + category: 'netflow', + name: 'netflow.section_exported_octets', + type: 'integer', + }, + 'netflow.dot1q_service_instance_tag': { + category: 'netflow', + name: 'netflow.dot1q_service_instance_tag', + type: 'short', + }, + 'netflow.dot1q_service_instance_id': { + category: 'netflow', + name: 'netflow.dot1q_service_instance_id', + type: 'long', + }, + 'netflow.dot1q_service_instance_priority': { + category: 'netflow', + name: 'netflow.dot1q_service_instance_priority', + type: 'short', + }, + 'netflow.dot1q_customer_source_mac_address': { + category: 'netflow', + name: 'netflow.dot1q_customer_source_mac_address', + type: 'keyword', + }, + 'netflow.dot1q_customer_destination_mac_address': { + category: 'netflow', + name: 'netflow.dot1q_customer_destination_mac_address', + type: 'keyword', + }, + 'netflow.post_layer2_octet_delta_count': { + category: 'netflow', + name: 'netflow.post_layer2_octet_delta_count', + type: 'long', + }, + 'netflow.post_mcast_layer2_octet_delta_count': { + category: 'netflow', + name: 'netflow.post_mcast_layer2_octet_delta_count', + type: 'long', + }, + 'netflow.post_layer2_octet_total_count': { + category: 'netflow', + name: 'netflow.post_layer2_octet_total_count', + type: 'long', + }, + 'netflow.post_mcast_layer2_octet_total_count': { + category: 'netflow', + name: 'netflow.post_mcast_layer2_octet_total_count', + type: 'long', + }, + 'netflow.minimum_layer2_total_length': { + category: 'netflow', + name: 'netflow.minimum_layer2_total_length', + type: 'long', + }, + 'netflow.maximum_layer2_total_length': { + category: 'netflow', + name: 'netflow.maximum_layer2_total_length', + type: 'long', + }, + 'netflow.dropped_layer2_octet_delta_count': { + category: 'netflow', + name: 'netflow.dropped_layer2_octet_delta_count', + type: 'long', + }, + 'netflow.dropped_layer2_octet_total_count': { + category: 'netflow', + name: 'netflow.dropped_layer2_octet_total_count', + type: 'long', + }, + 'netflow.ignored_layer2_octet_total_count': { + category: 'netflow', + name: 'netflow.ignored_layer2_octet_total_count', + type: 'long', + }, + 'netflow.not_sent_layer2_octet_total_count': { + category: 'netflow', + name: 'netflow.not_sent_layer2_octet_total_count', + type: 'long', + }, + 'netflow.layer2_octet_delta_sum_of_squares': { + category: 'netflow', + name: 'netflow.layer2_octet_delta_sum_of_squares', + type: 'long', + }, + 'netflow.layer2_octet_total_sum_of_squares': { + category: 'netflow', + name: 'netflow.layer2_octet_total_sum_of_squares', + type: 'long', + }, + 'netflow.layer2_frame_delta_count': { + category: 'netflow', + name: 'netflow.layer2_frame_delta_count', + type: 'long', + }, + 'netflow.layer2_frame_total_count': { + category: 'netflow', + name: 'netflow.layer2_frame_total_count', + type: 'long', + }, + 'netflow.pseudo_wire_destination_ipv4_address': { + category: 'netflow', + name: 'netflow.pseudo_wire_destination_ipv4_address', + type: 'ip', + }, + 'netflow.ignored_layer2_frame_total_count': { + category: 'netflow', + name: 'netflow.ignored_layer2_frame_total_count', + type: 'long', + }, + 'netflow.mib_object_value_integer': { + category: 'netflow', + name: 'netflow.mib_object_value_integer', + type: 'integer', + }, + 'netflow.mib_object_value_octet_string': { + category: 'netflow', + name: 'netflow.mib_object_value_octet_string', + type: 'short', + }, + 'netflow.mib_object_value_oid': { + category: 'netflow', + name: 'netflow.mib_object_value_oid', + type: 'short', + }, + 'netflow.mib_object_value_bits': { + category: 'netflow', + name: 'netflow.mib_object_value_bits', + type: 'short', + }, + 'netflow.mib_object_value_ip_address': { + category: 'netflow', + name: 'netflow.mib_object_value_ip_address', + type: 'ip', + }, + 'netflow.mib_object_value_counter': { + category: 'netflow', + name: 'netflow.mib_object_value_counter', + type: 'long', + }, + 'netflow.mib_object_value_gauge': { + category: 'netflow', + name: 'netflow.mib_object_value_gauge', + type: 'long', + }, + 'netflow.mib_object_value_time_ticks': { + category: 'netflow', + name: 'netflow.mib_object_value_time_ticks', + type: 'long', + }, + 'netflow.mib_object_value_unsigned': { + category: 'netflow', + name: 'netflow.mib_object_value_unsigned', + type: 'long', + }, + 'netflow.mib_object_identifier': { + category: 'netflow', + name: 'netflow.mib_object_identifier', + type: 'short', + }, + 'netflow.mib_sub_identifier': { + category: 'netflow', + name: 'netflow.mib_sub_identifier', + type: 'long', + }, + 'netflow.mib_index_indicator': { + category: 'netflow', + name: 'netflow.mib_index_indicator', + type: 'long', + }, + 'netflow.mib_capture_time_semantics': { + category: 'netflow', + name: 'netflow.mib_capture_time_semantics', + type: 'short', + }, + 'netflow.mib_context_engine_id': { + category: 'netflow', + name: 'netflow.mib_context_engine_id', + type: 'short', + }, + 'netflow.mib_context_name': { + category: 'netflow', + name: 'netflow.mib_context_name', + type: 'keyword', + }, + 'netflow.mib_object_name': { + category: 'netflow', + name: 'netflow.mib_object_name', + type: 'keyword', + }, + 'netflow.mib_object_description': { + category: 'netflow', + name: 'netflow.mib_object_description', + type: 'keyword', + }, + 'netflow.mib_object_syntax': { + category: 'netflow', + name: 'netflow.mib_object_syntax', + type: 'keyword', + }, + 'netflow.mib_module_name': { + category: 'netflow', + name: 'netflow.mib_module_name', + type: 'keyword', + }, + 'netflow.mobile_imsi': { + category: 'netflow', + name: 'netflow.mobile_imsi', + type: 'keyword', + }, + 'netflow.mobile_msisdn': { + category: 'netflow', + name: 'netflow.mobile_msisdn', + type: 'keyword', + }, + 'netflow.http_status_code': { + category: 'netflow', + name: 'netflow.http_status_code', + type: 'integer', + }, + 'netflow.source_transport_ports_limit': { + category: 'netflow', + name: 'netflow.source_transport_ports_limit', + type: 'integer', + }, + 'netflow.http_request_method': { + category: 'netflow', + name: 'netflow.http_request_method', + type: 'keyword', + }, + 'netflow.http_request_host': { + category: 'netflow', + name: 'netflow.http_request_host', + type: 'keyword', + }, + 'netflow.http_request_target': { + category: 'netflow', + name: 'netflow.http_request_target', + type: 'keyword', + }, + 'netflow.http_message_version': { + category: 'netflow', + name: 'netflow.http_message_version', + type: 'keyword', + }, + 'netflow.nat_instance_id': { + category: 'netflow', + name: 'netflow.nat_instance_id', + type: 'long', + }, + 'netflow.internal_address_realm': { + category: 'netflow', + name: 'netflow.internal_address_realm', + type: 'short', + }, + 'netflow.external_address_realm': { + category: 'netflow', + name: 'netflow.external_address_realm', + type: 'short', + }, + 'netflow.nat_quota_exceeded_event': { + category: 'netflow', + name: 'netflow.nat_quota_exceeded_event', + type: 'long', + }, + 'netflow.nat_threshold_event': { + category: 'netflow', + name: 'netflow.nat_threshold_event', + type: 'long', + }, + 'netflow.http_user_agent': { + category: 'netflow', + name: 'netflow.http_user_agent', + type: 'keyword', + }, + 'netflow.http_content_type': { + category: 'netflow', + name: 'netflow.http_content_type', + type: 'keyword', + }, + 'netflow.http_reason_phrase': { + category: 'netflow', + name: 'netflow.http_reason_phrase', + type: 'keyword', + }, + 'netflow.max_session_entries': { + category: 'netflow', + name: 'netflow.max_session_entries', + type: 'long', + }, + 'netflow.max_bib_entries': { + category: 'netflow', + name: 'netflow.max_bib_entries', + type: 'long', + }, + 'netflow.max_entries_per_user': { + category: 'netflow', + name: 'netflow.max_entries_per_user', + type: 'long', + }, + 'netflow.max_subscribers': { + category: 'netflow', + name: 'netflow.max_subscribers', + type: 'long', + }, + 'netflow.max_fragments_pending_reassembly': { + category: 'netflow', + name: 'netflow.max_fragments_pending_reassembly', + type: 'long', + }, + 'netflow.address_pool_high_threshold': { + category: 'netflow', + name: 'netflow.address_pool_high_threshold', + type: 'long', + }, + 'netflow.address_pool_low_threshold': { + category: 'netflow', + name: 'netflow.address_pool_low_threshold', + type: 'long', + }, + 'netflow.address_port_mapping_high_threshold': { + category: 'netflow', + name: 'netflow.address_port_mapping_high_threshold', + type: 'long', + }, + 'netflow.address_port_mapping_low_threshold': { + category: 'netflow', + name: 'netflow.address_port_mapping_low_threshold', + type: 'long', + }, + 'netflow.address_port_mapping_per_user_high_threshold': { + category: 'netflow', + name: 'netflow.address_port_mapping_per_user_high_threshold', + type: 'long', + }, + 'netflow.global_address_mapping_high_threshold': { + category: 'netflow', + name: 'netflow.global_address_mapping_high_threshold', + type: 'long', + }, + 'netflow.vpn_identifier': { + category: 'netflow', + name: 'netflow.vpn_identifier', + type: 'short', + }, + bucket_name: { + category: 'base', + description: 'Name of the S3 bucket that this log retrieved from. ', + name: 'bucket_name', + type: 'keyword', + }, + object_key: { + category: 'base', + description: 'Name of the S3 object that this log retrieved from. ', + name: 'object_key', + type: 'keyword', + }, + 'cef.version': { + category: 'cef', + description: 'Version of the CEF specification used by the message. ', + name: 'cef.version', + type: 'keyword', + }, + 'cef.device.vendor': { + category: 'cef', + description: 'Vendor of the device that produced the message. ', + name: 'cef.device.vendor', + type: 'keyword', + }, + 'cef.device.product': { + category: 'cef', + description: 'Product of the device that produced the message. ', + name: 'cef.device.product', + type: 'keyword', + }, + 'cef.device.version': { + category: 'cef', + description: 'Version of the product that produced the message. ', + name: 'cef.device.version', + type: 'keyword', + }, + 'cef.device.event_class_id': { + category: 'cef', + description: 'Unique identifier of the event type. ', + name: 'cef.device.event_class_id', + type: 'keyword', + }, + 'cef.severity': { + category: 'cef', + description: + 'Importance of the event. The valid string values are Unknown, Low, Medium, High, and Very-High. The valid integer values are 0-3=Low, 4-6=Medium, 7- 8=High, and 9-10=Very-High. ', + example: 'Very-High', + name: 'cef.severity', + type: 'keyword', + }, + 'cef.name': { + category: 'cef', + description: 'Short description of the event. ', + name: 'cef.name', + type: 'keyword', + }, + 'cef.extensions.agentAddress': { + category: 'cef', + description: 'The IP address of the ArcSight connector that processed the event.', + name: 'cef.extensions.agentAddress', + type: 'ip', + }, + 'cef.extensions.agentDnsDomain': { + category: 'cef', + description: 'The DNS domain name of the ArcSight connector that processed the event.', + name: 'cef.extensions.agentDnsDomain', + type: 'keyword', + }, + 'cef.extensions.agentHostName': { + category: 'cef', + description: 'The hostname of the ArcSight connector that processed the event.', + name: 'cef.extensions.agentHostName', + type: 'keyword', + }, + 'cef.extensions.agentId': { + category: 'cef', + description: 'The agent ID of the ArcSight connector that processed the event.', + name: 'cef.extensions.agentId', + type: 'keyword', + }, + 'cef.extensions.agentMacAddress': { + category: 'cef', + description: 'The MAC address of the ArcSight connector that processed the event.', + name: 'cef.extensions.agentMacAddress', + type: 'keyword', + }, + 'cef.extensions.agentNtDomain': { + category: 'cef', + description: 'null', + name: 'cef.extensions.agentNtDomain', + type: 'keyword', + }, + 'cef.extensions.agentReceiptTime': { + category: 'cef', + description: + 'The time at which information about the event was received by the ArcSight connector.', + name: 'cef.extensions.agentReceiptTime', + type: 'date', + }, + 'cef.extensions.agentTimeZone': { + category: 'cef', + description: 'The agent time zone of the ArcSight connector that processed the event.', + name: 'cef.extensions.agentTimeZone', + type: 'keyword', + }, + 'cef.extensions.agentTranslatedAddress': { + category: 'cef', + description: 'null', + name: 'cef.extensions.agentTranslatedAddress', + type: 'ip', + }, + 'cef.extensions.agentTranslatedZoneExternalID': { + category: 'cef', + description: 'null', + name: 'cef.extensions.agentTranslatedZoneExternalID', + type: 'keyword', + }, + 'cef.extensions.agentTranslatedZoneURI': { + category: 'cef', + description: 'null', + name: 'cef.extensions.agentTranslatedZoneURI', + type: 'keyword', + }, + 'cef.extensions.agentType': { + category: 'cef', + description: 'The agent type of the ArcSight connector that processed the event', + name: 'cef.extensions.agentType', + type: 'keyword', + }, + 'cef.extensions.agentVersion': { + category: 'cef', + description: 'The version of the ArcSight connector that processed the event.', + name: 'cef.extensions.agentVersion', + type: 'keyword', + }, + 'cef.extensions.agentZoneExternalID': { + category: 'cef', + description: 'null', + name: 'cef.extensions.agentZoneExternalID', + type: 'keyword', + }, + 'cef.extensions.agentZoneURI': { + category: 'cef', + description: 'null', + name: 'cef.extensions.agentZoneURI', + type: 'keyword', + }, + 'cef.extensions.applicationProtocol': { + category: 'cef', + description: + 'Application level protocol, example values are HTTP, HTTPS, SSHv2, Telnet, POP, IMPA, IMAPS, and so on.', + name: 'cef.extensions.applicationProtocol', + type: 'keyword', + }, + 'cef.extensions.baseEventCount': { + category: 'cef', + description: + 'A count associated with this event. How many times was this same event observed? Count can be omitted if it is 1.', + name: 'cef.extensions.baseEventCount', + type: 'long', + }, + 'cef.extensions.bytesIn': { + category: 'cef', + description: + 'Number of bytes transferred inbound, relative to the source to destination relationship, meaning that data was flowing from source to destination.', + name: 'cef.extensions.bytesIn', + type: 'long', + }, + 'cef.extensions.bytesOut': { + category: 'cef', + description: + 'Number of bytes transferred outbound relative to the source to destination relationship. For example, the byte number of data flowing from the destination to the source.', + name: 'cef.extensions.bytesOut', + type: 'long', + }, + 'cef.extensions.customerExternalID': { + category: 'cef', + description: 'null', + name: 'cef.extensions.customerExternalID', + type: 'keyword', + }, + 'cef.extensions.customerURI': { + category: 'cef', + description: 'null', + name: 'cef.extensions.customerURI', + type: 'keyword', + }, + 'cef.extensions.destinationAddress': { + category: 'cef', + description: + 'Identifies the destination address that the event refers to in an IP network. The format is an IPv4 address.', + name: 'cef.extensions.destinationAddress', + type: 'ip', + }, + 'cef.extensions.destinationDnsDomain': { + category: 'cef', + description: 'The DNS domain part of the complete fully qualified domain name (FQDN).', + name: 'cef.extensions.destinationDnsDomain', + type: 'keyword', + }, + 'cef.extensions.destinationGeoLatitude': { + category: 'cef', + description: "The latitudinal value from which the destination's IP address belongs.", + name: 'cef.extensions.destinationGeoLatitude', + type: 'double', + }, + 'cef.extensions.destinationGeoLongitude': { + category: 'cef', + description: "The longitudinal value from which the destination's IP address belongs.", + name: 'cef.extensions.destinationGeoLongitude', + type: 'double', + }, + 'cef.extensions.destinationHostName': { + category: 'cef', + description: + 'Identifies the destination that an event refers to in an IP network. The format should be a fully qualified domain name (FQDN) associated with the destination node, when a node is available.', + name: 'cef.extensions.destinationHostName', + type: 'keyword', + }, + 'cef.extensions.destinationMacAddress': { + category: 'cef', + description: 'Six colon-seperated hexadecimal numbers.', + name: 'cef.extensions.destinationMacAddress', + type: 'keyword', + }, + 'cef.extensions.destinationNtDomain': { + category: 'cef', + description: 'The Windows domain name of the destination address.', + name: 'cef.extensions.destinationNtDomain', + type: 'keyword', + }, + 'cef.extensions.destinationPort': { + category: 'cef', + description: 'The valid port numbers are between 0 and 65535.', + name: 'cef.extensions.destinationPort', + type: 'long', + }, + 'cef.extensions.destinationProcessId': { + category: 'cef', + description: + 'Provides the ID of the destination process associated with the event. For example, if an event contains process ID 105, "105" is the process ID.', + name: 'cef.extensions.destinationProcessId', + type: 'long', + }, + 'cef.extensions.destinationProcessName': { + category: 'cef', + description: "The name of the event's destination process.", + name: 'cef.extensions.destinationProcessName', + type: 'keyword', + }, + 'cef.extensions.destinationServiceName': { + category: 'cef', + description: 'The service targeted by this event.', + name: 'cef.extensions.destinationServiceName', + type: 'keyword', + }, + 'cef.extensions.destinationTranslatedAddress': { + category: 'cef', + description: 'Identifies the translated destination that the event refers to in an IP network.', + name: 'cef.extensions.destinationTranslatedAddress', + type: 'ip', + }, + 'cef.extensions.destinationTranslatedPort': { + category: 'cef', + description: + 'Port after it was translated; for example, a firewall. Valid port numbers are 0 to 65535.', + name: 'cef.extensions.destinationTranslatedPort', + type: 'long', + }, + 'cef.extensions.destinationTranslatedZoneExternalID': { + category: 'cef', + description: 'null', + name: 'cef.extensions.destinationTranslatedZoneExternalID', + type: 'keyword', + }, + 'cef.extensions.destinationTranslatedZoneURI': { + category: 'cef', + description: + 'The URI for the Translated Zone that the destination asset has been assigned to in ArcSight.', + name: 'cef.extensions.destinationTranslatedZoneURI', + type: 'keyword', + }, + 'cef.extensions.destinationUserId': { + category: 'cef', + description: + 'Identifies the destination user by ID. For example, in UNIX, the root user is generally associated with user ID 0.', + name: 'cef.extensions.destinationUserId', + type: 'keyword', + }, + 'cef.extensions.destinationUserName': { + category: 'cef', + description: + "Identifies the destination user by name. This is the user associated with the event's destination. Email addresses are often mapped into the UserName fields. The recipient is a candidate to put into this field.", + name: 'cef.extensions.destinationUserName', + type: 'keyword', + }, + 'cef.extensions.destinationUserPrivileges': { + category: 'cef', + description: + 'The typical values are "Administrator", "User", and "Guest". This identifies the destination user\'s privileges. In UNIX, for example, activity executed on the root user would be identified with destinationUser Privileges of "Administrator".', + name: 'cef.extensions.destinationUserPrivileges', + type: 'keyword', + }, + 'cef.extensions.destinationZoneExternalID': { + category: 'cef', + description: 'null', + name: 'cef.extensions.destinationZoneExternalID', + type: 'keyword', + }, + 'cef.extensions.destinationZoneURI': { + category: 'cef', + description: + 'The URI for the Zone that the destination asset has been assigned to in ArcSight.', + name: 'cef.extensions.destinationZoneURI', + type: 'keyword', + }, + 'cef.extensions.deviceAction': { + category: 'cef', + description: 'Action taken by the device.', + name: 'cef.extensions.deviceAction', + type: 'keyword', + }, + 'cef.extensions.deviceAddress': { + category: 'cef', + description: 'Identifies the device address that an event refers to in an IP network.', + name: 'cef.extensions.deviceAddress', + type: 'ip', + }, + 'cef.extensions.deviceCustomFloatingPoint1Label': { + category: 'cef', + description: + 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', + name: 'cef.extensions.deviceCustomFloatingPoint1Label', + type: 'keyword', + }, + 'cef.extensions.deviceCustomFloatingPoint3Label': { + category: 'cef', + description: + 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', + name: 'cef.extensions.deviceCustomFloatingPoint3Label', + type: 'keyword', + }, + 'cef.extensions.deviceCustomFloatingPoint4Label': { + category: 'cef', + description: + 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', + name: 'cef.extensions.deviceCustomFloatingPoint4Label', + type: 'keyword', + }, + 'cef.extensions.deviceCustomDate1': { + category: 'cef', + description: + 'One of two timestamp fields available to map fields that do not apply to any other in this dictionary.', + name: 'cef.extensions.deviceCustomDate1', + type: 'date', + }, + 'cef.extensions.deviceCustomDate1Label': { + category: 'cef', + description: + 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', + name: 'cef.extensions.deviceCustomDate1Label', + type: 'keyword', + }, + 'cef.extensions.deviceCustomDate2': { + category: 'cef', + description: + 'One of two timestamp fields available to map fields that do not apply to any other in this dictionary.', + name: 'cef.extensions.deviceCustomDate2', + type: 'date', + }, + 'cef.extensions.deviceCustomDate2Label': { + category: 'cef', + description: + 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', + name: 'cef.extensions.deviceCustomDate2Label', + type: 'keyword', + }, + 'cef.extensions.deviceCustomFloatingPoint1': { + category: 'cef', + description: + 'One of four floating point fields available to map fields that do not apply to any other in this dictionary.', + name: 'cef.extensions.deviceCustomFloatingPoint1', + type: 'double', + }, + 'cef.extensions.deviceCustomFloatingPoint2': { + category: 'cef', + description: + 'One of four floating point fields available to map fields that do not apply to any other in this dictionary.', + name: 'cef.extensions.deviceCustomFloatingPoint2', + type: 'double', + }, + 'cef.extensions.deviceCustomFloatingPoint2Label': { + category: 'cef', + description: + 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', + name: 'cef.extensions.deviceCustomFloatingPoint2Label', + type: 'keyword', + }, + 'cef.extensions.deviceCustomFloatingPoint3': { + category: 'cef', + description: + 'One of four floating point fields available to map fields that do not apply to any other in this dictionary.', + name: 'cef.extensions.deviceCustomFloatingPoint3', + type: 'double', + }, + 'cef.extensions.deviceCustomFloatingPoint4': { + category: 'cef', + description: + 'One of four floating point fields available to map fields that do not apply to any other in this dictionary.', + name: 'cef.extensions.deviceCustomFloatingPoint4', + type: 'double', + }, + 'cef.extensions.deviceCustomIPv6Address1': { + category: 'cef', + description: + 'One of four IPv6 address fields available to map fields that do not apply to any other in this dictionary.', + name: 'cef.extensions.deviceCustomIPv6Address1', + type: 'ip', + }, + 'cef.extensions.deviceCustomIPv6Address1Label': { + category: 'cef', + description: + 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', + name: 'cef.extensions.deviceCustomIPv6Address1Label', + type: 'keyword', + }, + 'cef.extensions.deviceCustomIPv6Address2': { + category: 'cef', + description: + 'One of four IPv6 address fields available to map fields that do not apply to any other in this dictionary.', + name: 'cef.extensions.deviceCustomIPv6Address2', + type: 'ip', + }, + 'cef.extensions.deviceCustomIPv6Address2Label': { + category: 'cef', + description: + 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', + name: 'cef.extensions.deviceCustomIPv6Address2Label', + type: 'keyword', + }, + 'cef.extensions.deviceCustomIPv6Address3': { + category: 'cef', + description: + 'One of four IPv6 address fields available to map fields that do not apply to any other in this dictionary.', + name: 'cef.extensions.deviceCustomIPv6Address3', + type: 'ip', + }, + 'cef.extensions.deviceCustomIPv6Address3Label': { + category: 'cef', + description: + 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', + name: 'cef.extensions.deviceCustomIPv6Address3Label', + type: 'keyword', + }, + 'cef.extensions.deviceCustomIPv6Address4': { + category: 'cef', + description: + 'One of four IPv6 address fields available to map fields that do not apply to any other in this dictionary.', + name: 'cef.extensions.deviceCustomIPv6Address4', + type: 'ip', + }, + 'cef.extensions.deviceCustomIPv6Address4Label': { + category: 'cef', + description: + 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', + name: 'cef.extensions.deviceCustomIPv6Address4Label', + type: 'keyword', + }, + 'cef.extensions.deviceCustomNumber1': { + category: 'cef', + description: + 'One of three number fields available to map fields that do not apply to any other in this dictionary. Use sparingly and seek a more specific, dictionary supplied field when possible.', + name: 'cef.extensions.deviceCustomNumber1', + type: 'long', + }, + 'cef.extensions.deviceCustomNumber1Label': { + category: 'cef', + description: + 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', + name: 'cef.extensions.deviceCustomNumber1Label', + type: 'keyword', + }, + 'cef.extensions.deviceCustomNumber2': { + category: 'cef', + description: + 'One of three number fields available to map fields that do not apply to any other in this dictionary. Use sparingly and seek a more specific, dictionary supplied field when possible.', + name: 'cef.extensions.deviceCustomNumber2', + type: 'long', + }, + 'cef.extensions.deviceCustomNumber2Label': { + category: 'cef', + description: + 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', + name: 'cef.extensions.deviceCustomNumber2Label', + type: 'keyword', + }, + 'cef.extensions.deviceCustomNumber3': { + category: 'cef', + description: + 'One of three number fields available to map fields that do not apply to any other in this dictionary. Use sparingly and seek a more specific, dictionary supplied field when possible.', + name: 'cef.extensions.deviceCustomNumber3', + type: 'long', + }, + 'cef.extensions.deviceCustomNumber3Label': { + category: 'cef', + description: + 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', + name: 'cef.extensions.deviceCustomNumber3Label', + type: 'keyword', + }, + 'cef.extensions.deviceCustomString1': { + category: 'cef', + description: + 'One of six strings available to map fields that do not apply to any other in this dictionary. Use sparingly and seek a more specific, dictionary supplied field when possible.', + name: 'cef.extensions.deviceCustomString1', + type: 'keyword', + }, + 'cef.extensions.deviceCustomString1Label': { + category: 'cef', + description: + 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', + name: 'cef.extensions.deviceCustomString1Label', + type: 'keyword', + }, + 'cef.extensions.deviceCustomString2': { + category: 'cef', + description: + 'One of six strings available to map fields that do not apply to any other in this dictionary. Use sparingly and seek a more specific, dictionary supplied field when possible.', + name: 'cef.extensions.deviceCustomString2', + type: 'keyword', + }, + 'cef.extensions.deviceCustomString2Label': { + category: 'cef', + description: + 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', + name: 'cef.extensions.deviceCustomString2Label', + type: 'keyword', + }, + 'cef.extensions.deviceCustomString3': { + category: 'cef', + description: + 'One of six strings available to map fields that do not apply to any other in this dictionary. Use sparingly and seek a more specific, dictionary supplied field when possible.', + name: 'cef.extensions.deviceCustomString3', + type: 'keyword', + }, + 'cef.extensions.deviceCustomString3Label': { + category: 'cef', + description: + 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', + name: 'cef.extensions.deviceCustomString3Label', + type: 'keyword', + }, + 'cef.extensions.deviceCustomString4': { + category: 'cef', + description: + 'One of six strings available to map fields that do not apply to any other in this dictionary. Use sparingly and seek a more specific, dictionary supplied field when possible.', + name: 'cef.extensions.deviceCustomString4', + type: 'keyword', + }, + 'cef.extensions.deviceCustomString4Label': { + category: 'cef', + description: + 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', + name: 'cef.extensions.deviceCustomString4Label', + type: 'keyword', + }, + 'cef.extensions.deviceCustomString5': { + category: 'cef', + description: + 'One of six strings available to map fields that do not apply to any other in this dictionary. Use sparingly and seek a more specific, dictionary supplied field when possible.', + name: 'cef.extensions.deviceCustomString5', + type: 'keyword', + }, + 'cef.extensions.deviceCustomString5Label': { + category: 'cef', + description: + 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', + name: 'cef.extensions.deviceCustomString5Label', + type: 'keyword', + }, + 'cef.extensions.deviceCustomString6': { + category: 'cef', + description: + 'One of six strings available to map fields that do not apply to any other in this dictionary. Use sparingly and seek a more specific, dictionary supplied field when possible.', + name: 'cef.extensions.deviceCustomString6', + type: 'keyword', + }, + 'cef.extensions.deviceCustomString6Label': { + category: 'cef', + description: + 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', + name: 'cef.extensions.deviceCustomString6Label', + type: 'keyword', + }, + 'cef.extensions.deviceDirection': { + category: 'cef', + description: + 'Any information about what direction the observed communication has taken. The following values are supported - "0" for inbound or "1" for outbound.', + name: 'cef.extensions.deviceDirection', + type: 'long', + }, + 'cef.extensions.deviceDnsDomain': { + category: 'cef', + description: 'The DNS domain part of the complete fully qualified domain name (FQDN).', + name: 'cef.extensions.deviceDnsDomain', + type: 'keyword', + }, + 'cef.extensions.deviceEventCategory': { + category: 'cef', + description: + 'Represents the category assigned by the originating device. Devices often use their own categorization schema to classify event. Example "/Monitor/Disk/Read".', + name: 'cef.extensions.deviceEventCategory', + type: 'keyword', + }, + 'cef.extensions.deviceExternalId': { + category: 'cef', + description: 'A name that uniquely identifies the device generating this event.', + name: 'cef.extensions.deviceExternalId', + type: 'keyword', + }, + 'cef.extensions.deviceFacility': { + category: 'cef', + description: + 'The facility generating this event. For example, Syslog has an explicit facility associated with every event.', + name: 'cef.extensions.deviceFacility', + type: 'keyword', + }, + 'cef.extensions.deviceFlexNumber1': { + category: 'cef', + description: + 'One of two alternative number fields available to map fields that do not apply to any other in this dictionary. Use sparingly and seek a more specific, dictionary supplied field when possible.', + name: 'cef.extensions.deviceFlexNumber1', + type: 'long', + }, + 'cef.extensions.deviceFlexNumber1Label': { + category: 'cef', + description: + 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', + name: 'cef.extensions.deviceFlexNumber1Label', + type: 'keyword', + }, + 'cef.extensions.deviceFlexNumber2': { + category: 'cef', + description: + 'One of two alternative number fields available to map fields that do not apply to any other in this dictionary. Use sparingly and seek a more specific, dictionary supplied field when possible.', + name: 'cef.extensions.deviceFlexNumber2', + type: 'long', + }, + 'cef.extensions.deviceFlexNumber2Label': { + category: 'cef', + description: + 'All custom fields have a corresponding label field. Each of these fields is a string and describes the purpose of the custom field.', + name: 'cef.extensions.deviceFlexNumber2Label', + type: 'keyword', + }, + 'cef.extensions.deviceHostName': { + category: 'cef', + description: + 'The format should be a fully qualified domain name (FQDN) associated with the device node, when a node is available.', + name: 'cef.extensions.deviceHostName', + type: 'keyword', + }, + 'cef.extensions.deviceInboundInterface': { + category: 'cef', + description: 'Interface on which the packet or data entered the device.', + name: 'cef.extensions.deviceInboundInterface', + type: 'keyword', + }, + 'cef.extensions.deviceMacAddress': { + category: 'cef', + description: 'Six colon-separated hexadecimal numbers.', + name: 'cef.extensions.deviceMacAddress', + type: 'keyword', + }, + 'cef.extensions.deviceNtDomain': { + category: 'cef', + description: 'The Windows domain name of the device address.', + name: 'cef.extensions.deviceNtDomain', + type: 'keyword', + }, + 'cef.extensions.deviceOutboundInterface': { + category: 'cef', + description: 'Interface on which the packet or data left the device.', + name: 'cef.extensions.deviceOutboundInterface', + type: 'keyword', + }, + 'cef.extensions.devicePayloadId': { + category: 'cef', + description: 'Unique identifier for the payload associated with the event.', + name: 'cef.extensions.devicePayloadId', + type: 'keyword', + }, + 'cef.extensions.deviceProcessId': { + category: 'cef', + description: 'Provides the ID of the process on the device generating the event.', + name: 'cef.extensions.deviceProcessId', + type: 'long', + }, + 'cef.extensions.deviceProcessName': { + category: 'cef', + description: + 'Process name associated with the event. An example might be the process generating the syslog entry in UNIX.', + name: 'cef.extensions.deviceProcessName', + type: 'keyword', + }, + 'cef.extensions.deviceReceiptTime': { + category: 'cef', + description: + 'The time at which the event related to the activity was received. The format is MMM dd yyyy HH:mm:ss or milliseconds since epoch (Jan 1st 1970)', + name: 'cef.extensions.deviceReceiptTime', + type: 'date', + }, + 'cef.extensions.deviceTimeZone': { + category: 'cef', + description: 'The time zone for the device generating the event.', + name: 'cef.extensions.deviceTimeZone', + type: 'keyword', + }, + 'cef.extensions.deviceTranslatedAddress': { + category: 'cef', + description: + 'Identifies the translated device address that the event refers to in an IP network.', + name: 'cef.extensions.deviceTranslatedAddress', + type: 'ip', + }, + 'cef.extensions.deviceTranslatedZoneExternalID': { + category: 'cef', + description: 'null', + name: 'cef.extensions.deviceTranslatedZoneExternalID', + type: 'keyword', + }, + 'cef.extensions.deviceTranslatedZoneURI': { + category: 'cef', + description: + 'The URI for the Translated Zone that the device asset has been assigned to in ArcSight.', + name: 'cef.extensions.deviceTranslatedZoneURI', + type: 'keyword', + }, + 'cef.extensions.deviceZoneExternalID': { + category: 'cef', + description: 'null', + name: 'cef.extensions.deviceZoneExternalID', + type: 'keyword', + }, + 'cef.extensions.deviceZoneURI': { + category: 'cef', + description: 'Thee URI for the Zone that the device asset has been assigned to in ArcSight.', + name: 'cef.extensions.deviceZoneURI', + type: 'keyword', + }, + 'cef.extensions.endTime': { + category: 'cef', + description: + 'The time at which the activity related to the event ended. The format is MMM dd yyyy HH:mm:ss or milliseconds since epoch (Jan 1st1970). An example would be reporting the end of a session.', + name: 'cef.extensions.endTime', + type: 'date', + }, + 'cef.extensions.eventId': { + category: 'cef', + description: 'This is a unique ID that ArcSight assigns to each event.', + name: 'cef.extensions.eventId', + type: 'long', + }, + 'cef.extensions.eventOutcome': { + category: 'cef', + description: "Displays the outcome, usually as 'success' or 'failure'.", + name: 'cef.extensions.eventOutcome', + type: 'keyword', + }, + 'cef.extensions.externalId': { + category: 'cef', + description: + 'The ID used by an originating device. They are usually increasing numbers, associated with events.', + name: 'cef.extensions.externalId', + type: 'keyword', + }, + 'cef.extensions.fileCreateTime': { + category: 'cef', + description: 'Time when the file was created.', + name: 'cef.extensions.fileCreateTime', + type: 'date', + }, + 'cef.extensions.fileHash': { + category: 'cef', + description: 'Hash of a file.', + name: 'cef.extensions.fileHash', + type: 'keyword', + }, + 'cef.extensions.fileId': { + category: 'cef', + description: 'An ID associated with a file could be the inode.', + name: 'cef.extensions.fileId', + type: 'keyword', + }, + 'cef.extensions.fileModificationTime': { + category: 'cef', + description: 'Time when the file was last modified.', + name: 'cef.extensions.fileModificationTime', + type: 'date', + }, + 'cef.extensions.filename': { + category: 'cef', + description: 'Name of the file only (without its path).', + name: 'cef.extensions.filename', + type: 'keyword', + }, + 'cef.extensions.filePath': { + category: 'cef', + description: 'Full path to the file, including file name itself.', + name: 'cef.extensions.filePath', + type: 'keyword', + }, + 'cef.extensions.filePermission': { + category: 'cef', + description: 'Permissions of the file.', + name: 'cef.extensions.filePermission', + type: 'keyword', + }, + 'cef.extensions.fileSize': { + category: 'cef', + description: 'Size of the file.', + name: 'cef.extensions.fileSize', + type: 'long', + }, + 'cef.extensions.fileType': { + category: 'cef', + description: 'Type of file (pipe, socket, etc.)', + name: 'cef.extensions.fileType', + type: 'keyword', + }, + 'cef.extensions.flexDate1': { + category: 'cef', + description: + 'A timestamp field available to map a timestamp that does not apply to any other defined timestamp field in this dictionary. Use all flex fields sparingly and seek a more specific, dictionary supplied field when possible. These fields are typically reserved for customer use and should not be set by vendors unless necessary.', + name: 'cef.extensions.flexDate1', + type: 'date', + }, + 'cef.extensions.flexDate1Label': { + category: 'cef', + description: 'The label field is a string and describes the purpose of the flex field.', + name: 'cef.extensions.flexDate1Label', + type: 'keyword', + }, + 'cef.extensions.flexString1': { + category: 'cef', + description: + 'One of four floating point fields available to map fields that do not apply to any other in this dictionary. Use sparingly and seek a more specific, dictionary supplied field when possible. These fields are typically reserved for customer use and should not be set by vendors unless necessary.', + name: 'cef.extensions.flexString1', + type: 'keyword', + }, + 'cef.extensions.flexString2': { + category: 'cef', + description: + 'One of four floating point fields available to map fields that do not apply to any other in this dictionary. Use sparingly and seek a more specific, dictionary supplied field when possible. These fields are typically reserved for customer use and should not be set by vendors unless necessary.', + name: 'cef.extensions.flexString2', + type: 'keyword', + }, + 'cef.extensions.flexString1Label': { + category: 'cef', + description: 'The label field is a string and describes the purpose of the flex field.', + name: 'cef.extensions.flexString1Label', + type: 'keyword', + }, + 'cef.extensions.flexString2Label': { + category: 'cef', + description: 'The label field is a string and describes the purpose of the flex field.', + name: 'cef.extensions.flexString2Label', + type: 'keyword', + }, + 'cef.extensions.message': { + category: 'cef', + description: + 'An arbitrary message giving more details about the event. Multi-line entries can be produced by using \\n as the new line separator.', + name: 'cef.extensions.message', + type: 'keyword', + }, + 'cef.extensions.oldFileCreateTime': { + category: 'cef', + description: 'Time when old file was created.', + name: 'cef.extensions.oldFileCreateTime', + type: 'date', + }, + 'cef.extensions.oldFileHash': { + category: 'cef', + description: 'Hash of the old file.', + name: 'cef.extensions.oldFileHash', + type: 'keyword', + }, + 'cef.extensions.oldFileId': { + category: 'cef', + description: 'An ID associated with the old file could be the inode.', + name: 'cef.extensions.oldFileId', + type: 'keyword', + }, + 'cef.extensions.oldFileModificationTime': { + category: 'cef', + description: 'Time when old file was last modified.', + name: 'cef.extensions.oldFileModificationTime', + type: 'date', + }, + 'cef.extensions.oldFileName': { + category: 'cef', + description: 'Name of the old file.', + name: 'cef.extensions.oldFileName', + type: 'keyword', + }, + 'cef.extensions.oldFilePath': { + category: 'cef', + description: 'Full path to the old file, including the file name itself.', + name: 'cef.extensions.oldFilePath', + type: 'keyword', + }, + 'cef.extensions.oldFilePermission': { + category: 'cef', + description: 'Permissions of the old file.', + name: 'cef.extensions.oldFilePermission', + type: 'keyword', + }, + 'cef.extensions.oldFileSize': { + category: 'cef', + description: 'Size of the old file.', + name: 'cef.extensions.oldFileSize', + type: 'long', + }, + 'cef.extensions.oldFileType': { + category: 'cef', + description: 'Type of the old file (pipe, socket, etc.)', + name: 'cef.extensions.oldFileType', + type: 'keyword', + }, + 'cef.extensions.rawEvent': { + category: 'cef', + description: 'null', + name: 'cef.extensions.rawEvent', + type: 'keyword', + }, + 'cef.extensions.Reason': { + category: 'cef', + description: + 'The reason an audit event was generated. For example "bad password" or "unknown user". This could also be an error or return code. Example "0x1234".', + name: 'cef.extensions.Reason', + type: 'keyword', + }, + 'cef.extensions.requestClientApplication': { + category: 'cef', + description: 'The User-Agent associated with the request.', + name: 'cef.extensions.requestClientApplication', + type: 'keyword', + }, + 'cef.extensions.requestContext': { + category: 'cef', + description: + 'Description of the content from which the request originated (for example, HTTP Referrer)', + name: 'cef.extensions.requestContext', + type: 'keyword', + }, + 'cef.extensions.requestCookies': { + category: 'cef', + description: 'Cookies associated with the request.', + name: 'cef.extensions.requestCookies', + type: 'keyword', + }, + 'cef.extensions.requestMethod': { + category: 'cef', + description: 'The HTTP method used to access a URL.', + name: 'cef.extensions.requestMethod', + type: 'keyword', + }, + 'cef.extensions.requestUrl': { + category: 'cef', + description: + 'In the case of an HTTP request, this field contains the URL accessed. The URL should contain the protocol as well.', + name: 'cef.extensions.requestUrl', + type: 'keyword', + }, + 'cef.extensions.sourceAddress': { + category: 'cef', + description: 'Identifies the source that an event refers to in an IP network.', + name: 'cef.extensions.sourceAddress', + type: 'ip', + }, + 'cef.extensions.sourceDnsDomain': { + category: 'cef', + description: 'The DNS domain part of the complete fully qualified domain name (FQDN).', + name: 'cef.extensions.sourceDnsDomain', + type: 'keyword', + }, + 'cef.extensions.sourceGeoLatitude': { + category: 'cef', + description: 'null', + name: 'cef.extensions.sourceGeoLatitude', + type: 'double', + }, + 'cef.extensions.sourceGeoLongitude': { + category: 'cef', + description: 'null', + name: 'cef.extensions.sourceGeoLongitude', + type: 'double', + }, + 'cef.extensions.sourceHostName': { + category: 'cef', + description: + "Identifies the source that an event refers to in an IP network. The format should be a fully qualified domain name (FQDN) associated with the source node, when a mode is available. Examples: 'host' or 'host.domain.com'. ", + name: 'cef.extensions.sourceHostName', + type: 'keyword', + }, + 'cef.extensions.sourceMacAddress': { + category: 'cef', + description: 'Six colon-separated hexadecimal numbers.', + example: '00:0d:60:af:1b:61', + name: 'cef.extensions.sourceMacAddress', + type: 'keyword', + }, + 'cef.extensions.sourceNtDomain': { + category: 'cef', + description: 'The Windows domain name for the source address.', + name: 'cef.extensions.sourceNtDomain', + type: 'keyword', + }, + 'cef.extensions.sourcePort': { + category: 'cef', + description: 'The valid port numbers are 0 to 65535.', + name: 'cef.extensions.sourcePort', + type: 'long', + }, + 'cef.extensions.sourceProcessId': { + category: 'cef', + description: 'The ID of the source process associated with the event.', + name: 'cef.extensions.sourceProcessId', + type: 'long', + }, + 'cef.extensions.sourceProcessName': { + category: 'cef', + description: "The name of the event's source process.", + name: 'cef.extensions.sourceProcessName', + type: 'keyword', + }, + 'cef.extensions.sourceServiceName': { + category: 'cef', + description: 'The service that is responsible for generating this event.', + name: 'cef.extensions.sourceServiceName', + type: 'keyword', + }, + 'cef.extensions.sourceTranslatedAddress': { + category: 'cef', + description: 'Identifies the translated source that the event refers to in an IP network.', + name: 'cef.extensions.sourceTranslatedAddress', + type: 'ip', + }, + 'cef.extensions.sourceTranslatedPort': { + category: 'cef', + description: + 'A port number after being translated by, for example, a firewall. Valid port numbers are 0 to 65535.', + name: 'cef.extensions.sourceTranslatedPort', + type: 'long', + }, + 'cef.extensions.sourceTranslatedZoneExternalID': { + category: 'cef', + description: 'null', + name: 'cef.extensions.sourceTranslatedZoneExternalID', + type: 'keyword', + }, + 'cef.extensions.sourceTranslatedZoneURI': { + category: 'cef', + description: + 'The URI for the Translated Zone that the destination asset has been assigned to in ArcSight.', + name: 'cef.extensions.sourceTranslatedZoneURI', + type: 'keyword', + }, + 'cef.extensions.sourceUserId': { + category: 'cef', + description: + 'Identifies the source user by ID. This is the user associated with the source of the event. For example, in UNIX, the root user is generally associated with user ID 0.', + name: 'cef.extensions.sourceUserId', + type: 'keyword', + }, + 'cef.extensions.sourceUserName': { + category: 'cef', + description: + 'Identifies the source user by name. Email addresses are also mapped into the UserName fields. The sender is a candidate to put into this field.', + name: 'cef.extensions.sourceUserName', + type: 'keyword', + }, + 'cef.extensions.sourceUserPrivileges': { + category: 'cef', + description: + 'The typical values are "Administrator", "User", and "Guest". It identifies the source user\'s privileges. In UNIX, for example, activity executed by the root user would be identified with "Administrator".', + name: 'cef.extensions.sourceUserPrivileges', + type: 'keyword', + }, + 'cef.extensions.sourceZoneExternalID': { + category: 'cef', + description: 'null', + name: 'cef.extensions.sourceZoneExternalID', + type: 'keyword', + }, + 'cef.extensions.sourceZoneURI': { + category: 'cef', + description: 'The URI for the Zone that the source asset has been assigned to in ArcSight.', + name: 'cef.extensions.sourceZoneURI', + type: 'keyword', + }, + 'cef.extensions.startTime': { + category: 'cef', + description: + 'The time when the activity the event referred to started. The format is MMM dd yyyy HH:mm:ss or milliseconds since epoch (Jan 1st 1970)', + name: 'cef.extensions.startTime', + type: 'date', + }, + 'cef.extensions.transportProtocol': { + category: 'cef', + description: + 'Identifies the Layer-4 protocol used. The possible values are protocols such as TCP or UDP.', + name: 'cef.extensions.transportProtocol', + type: 'keyword', + }, + 'cef.extensions.type': { + category: 'cef', + description: + '0 means base event, 1 means aggregated, 2 means correlation, and 3 means action. This field can be omitted for base events (type 0).', + name: 'cef.extensions.type', + type: 'long', + }, + 'cef.extensions.categoryDeviceType': { + category: 'cef', + description: 'Device type. Examples - Proxy, IDS, Web Server', + name: 'cef.extensions.categoryDeviceType', + type: 'keyword', + }, + 'cef.extensions.categoryObject': { + category: 'cef', + description: + 'Object that the event is about. For example it can be an operating sytem, database, file, etc.', + name: 'cef.extensions.categoryObject', + type: 'keyword', + }, + 'cef.extensions.categoryBehavior': { + category: 'cef', + description: + "Action or a behavior associated with an event. It's what is being done to the object.", + name: 'cef.extensions.categoryBehavior', + type: 'keyword', + }, + 'cef.extensions.categoryTechnique': { + category: 'cef', + description: 'Technique being used (e.g. /DoS).', + name: 'cef.extensions.categoryTechnique', + type: 'keyword', + }, + 'cef.extensions.categoryDeviceGroup': { + category: 'cef', + description: 'General device group like Firewall.', + name: 'cef.extensions.categoryDeviceGroup', + type: 'keyword', + }, + 'cef.extensions.categorySignificance': { + category: 'cef', + description: 'Characterization of the importance of the event.', + name: 'cef.extensions.categorySignificance', + type: 'keyword', + }, + 'cef.extensions.categoryOutcome': { + category: 'cef', + description: 'Outcome of the event (e.g. sucess, failure, or attempt).', + name: 'cef.extensions.categoryOutcome', + type: 'keyword', + }, + 'cef.extensions.managerReceiptTime': { + category: 'cef', + description: 'When the Arcsight ESM received the event.', + name: 'cef.extensions.managerReceiptTime', + type: 'date', + }, + 'source.service.name': { + category: 'source', + description: 'Service that is the source of the event.', + name: 'source.service.name', + type: 'keyword', + }, + 'destination.service.name': { + category: 'destination', + description: 'Service that is the target of the event.', + name: 'destination.service.name', + type: 'keyword', + }, + type: { + category: 'base', + description: + 'The type of the transaction (for example, HTTP, MySQL, Redis, or RUM) or "flow" in case of flows. ', + name: 'type', + }, + 'server.process.name': { + category: 'server', + description: 'The name of the process that served the transaction. ', + name: 'server.process.name', + }, + 'server.process.args': { + category: 'server', + description: 'The command-line of the process that served the transaction. ', + name: 'server.process.args', + }, + 'server.process.executable': { + category: 'server', + description: 'Absolute path to the server process executable. ', + name: 'server.process.executable', + }, + 'server.process.working_directory': { + category: 'server', + description: 'The working directory of the server process. ', + name: 'server.process.working_directory', + }, + 'server.process.start': { + category: 'server', + description: 'The time the server process started. ', + name: 'server.process.start', + }, + 'client.process.name': { + category: 'client', + description: 'The name of the process that initiated the transaction. ', + name: 'client.process.name', + }, + 'client.process.args': { + category: 'client', + description: 'The command-line of the process that initiated the transaction. ', + name: 'client.process.args', + }, + 'client.process.executable': { + category: 'client', + description: 'Absolute path to the client process executable. ', + name: 'client.process.executable', + }, + 'client.process.working_directory': { + category: 'client', + description: 'The working directory of the client process. ', + name: 'client.process.working_directory', + }, + 'client.process.start': { + category: 'client', + description: 'The time the client process started. ', + name: 'client.process.start', + }, + real_ip: { + category: 'base', + description: + 'If the server initiating the transaction is a proxy, this field contains the original client IP address. For HTTP, for example, the IP address extracted from a configurable HTTP header, by default `X-Forwarded-For`. Unless this field is disabled, it always has a value, and it matches the `client_ip` for non proxy clients. ', + name: 'real_ip', + type: 'alias', + }, + transport: { + category: 'base', + description: + 'The transport protocol used for the transaction. If not specified, then tcp is assumed. ', + name: 'transport', + type: 'alias', + }, + 'flow.final': { + category: 'flow', + description: + 'Indicates if event is last event in flow. If final is false, the event reports an intermediate flow state only. ', + name: 'flow.final', + type: 'boolean', + }, + 'flow.id': { + category: 'flow', + description: 'Internal flow ID based on connection meta data and address. ', + name: 'flow.id', + }, + 'flow.vlan': { + category: 'flow', + description: + "VLAN identifier from the 802.1q frame. In case of a multi-tagged frame this field will be an array with the outer tag's VLAN identifier listed first. ", + name: 'flow.vlan', + type: 'long', + }, + flow_id: { + category: 'base', + name: 'flow_id', + type: 'alias', + }, + final: { + category: 'base', + name: 'final', + type: 'alias', + }, + vlan: { + category: 'base', + name: 'vlan', + type: 'alias', + }, + 'source.stats.net_bytes_total': { + category: 'source', + name: 'source.stats.net_bytes_total', + type: 'alias', + }, + 'source.stats.net_packets_total': { + category: 'source', + name: 'source.stats.net_packets_total', + type: 'alias', + }, + 'dest.stats.net_bytes_total': { + category: 'dest', + name: 'dest.stats.net_bytes_total', + type: 'alias', + }, + 'dest.stats.net_packets_total': { + category: 'dest', + name: 'dest.stats.net_packets_total', + type: 'alias', + }, + status: { + category: 'base', + description: + 'The high level status of the transaction. The way to compute this value depends on the protocol, but the result has a meaning independent of the protocol. ', + name: 'status', + }, + method: { + category: 'base', + description: + 'The command/verb/method of the transaction. For HTTP, this is the method name (GET, POST, PUT, and so on), for SQL this is the verb (SELECT, UPDATE, DELETE, and so on). ', + name: 'method', + }, + resource: { + category: 'base', + description: + 'The logical resource that this transaction refers to. For HTTP, this is the URL path up to the last slash (/). For example, if the URL is `/users/1`, the resource is `/users`. For databases, the resource is typically the table name. The field is not filled for all transaction types. ', + name: 'resource', + }, + path: { + category: 'base', + description: + 'The path the transaction refers to. For HTTP, this is the URL. For SQL databases, this is the table name. For key-value stores, this is the key. ', + name: 'path', + }, + query: { + category: 'base', + description: + 'The query in a human readable format. For HTTP, it will typically be something like `GET /users/_search?name=test`. For MySQL, it is something like `SELECT id from users where name=test`. ', + name: 'query', + type: 'keyword', + }, + params: { + category: 'base', + description: + 'The request parameters. For HTTP, these are the POST or GET parameters. For Thrift-RPC, these are the parameters from the request. ', + name: 'params', + type: 'text', + }, + notes: { + category: 'base', + description: + 'Messages from Packetbeat itself. This field usually contains error messages for interpreting the raw data. This information can be helpful for troubleshooting. ', + name: 'notes', + type: 'alias', + }, + request: { + category: 'base', + description: + 'For text protocols, this is the request as seen on the wire (application layer only). For binary protocols this is our representation of the request. ', + name: 'request', + type: 'text', + }, + response: { + category: 'base', + description: + 'For text protocols, this is the response as seen on the wire (application layer only). For binary protocols this is our representation of the request. ', + name: 'response', + type: 'text', + }, + bytes_in: { + category: 'base', + description: + 'The number of bytes of the request. Note that this size is the application layer message length, without the length of the IP or TCP headers. ', + name: 'bytes_in', + type: 'alias', + }, + bytes_out: { + category: 'base', + description: + 'The number of bytes of the response. Note that this size is the application layer message length, without the length of the IP or TCP headers. ', + name: 'bytes_out', + type: 'alias', + }, + 'amqp.reply-code': { + category: 'amqp', + description: 'AMQP reply code to an error, similar to http reply-code ', + example: 404, + name: 'amqp.reply-code', + type: 'long', + }, + 'amqp.reply-text': { + category: 'amqp', + description: 'Text explaining the error. ', + name: 'amqp.reply-text', + type: 'keyword', + }, + 'amqp.class-id': { + category: 'amqp', + description: 'Failing method class. ', + name: 'amqp.class-id', + type: 'long', + }, + 'amqp.method-id': { + category: 'amqp', + description: 'Failing method ID. ', + name: 'amqp.method-id', + type: 'long', + }, + 'amqp.exchange': { + category: 'amqp', + description: 'Name of the exchange. ', + name: 'amqp.exchange', + type: 'keyword', + }, + 'amqp.exchange-type': { + category: 'amqp', + description: 'Exchange type. ', + example: 'fanout', + name: 'amqp.exchange-type', + type: 'keyword', + }, + 'amqp.passive': { + category: 'amqp', + description: 'If set, do not create exchange/queue. ', + name: 'amqp.passive', + type: 'boolean', + }, + 'amqp.durable': { + category: 'amqp', + description: 'If set, request a durable exchange/queue. ', + name: 'amqp.durable', + type: 'boolean', + }, + 'amqp.exclusive': { + category: 'amqp', + description: 'If set, request an exclusive queue. ', + name: 'amqp.exclusive', + type: 'boolean', + }, + 'amqp.auto-delete': { + category: 'amqp', + description: 'If set, auto-delete queue when unused. ', + name: 'amqp.auto-delete', + type: 'boolean', + }, + 'amqp.no-wait': { + category: 'amqp', + description: 'If set, the server will not respond to the method. ', + name: 'amqp.no-wait', + type: 'boolean', + }, + 'amqp.consumer-tag': { + category: 'amqp', + description: 'Identifier for the consumer, valid within the current channel. ', + name: 'amqp.consumer-tag', + }, + 'amqp.delivery-tag': { + category: 'amqp', + description: 'The server-assigned and channel-specific delivery tag. ', + name: 'amqp.delivery-tag', + type: 'long', + }, + 'amqp.message-count': { + category: 'amqp', + description: + 'The number of messages in the queue, which will be zero for newly-declared queues. ', + name: 'amqp.message-count', + type: 'long', + }, + 'amqp.consumer-count': { + category: 'amqp', + description: 'The number of consumers of a queue. ', + name: 'amqp.consumer-count', + type: 'long', + }, + 'amqp.routing-key': { + category: 'amqp', + description: 'Message routing key. ', + name: 'amqp.routing-key', + type: 'keyword', + }, + 'amqp.no-ack': { + category: 'amqp', + description: 'If set, the server does not expect acknowledgements for messages. ', + name: 'amqp.no-ack', + type: 'boolean', + }, + 'amqp.no-local': { + category: 'amqp', + description: + 'If set, the server will not send messages to the connection that published them. ', + name: 'amqp.no-local', + type: 'boolean', + }, + 'amqp.if-unused': { + category: 'amqp', + description: 'Delete only if unused. ', + name: 'amqp.if-unused', + type: 'boolean', + }, + 'amqp.if-empty': { + category: 'amqp', + description: 'Delete only if empty. ', + name: 'amqp.if-empty', + type: 'boolean', + }, + 'amqp.queue': { + category: 'amqp', + description: 'The queue name identifies the queue within the vhost. ', + name: 'amqp.queue', + type: 'keyword', + }, + 'amqp.redelivered': { + category: 'amqp', + description: + 'Indicates that the message has been previously delivered to this or another client. ', + name: 'amqp.redelivered', + type: 'boolean', + }, + 'amqp.multiple': { + category: 'amqp', + description: 'Acknowledge multiple messages. ', + name: 'amqp.multiple', + type: 'boolean', + }, + 'amqp.arguments': { + category: 'amqp', + description: 'Optional additional arguments passed to some methods. Can be of various types. ', + name: 'amqp.arguments', + type: 'object', + }, + 'amqp.mandatory': { + category: 'amqp', + description: 'Indicates mandatory routing. ', + name: 'amqp.mandatory', + type: 'boolean', + }, + 'amqp.immediate': { + category: 'amqp', + description: 'Request immediate delivery. ', + name: 'amqp.immediate', + type: 'boolean', + }, + 'amqp.content-type': { + category: 'amqp', + description: 'MIME content type. ', + example: 'text/plain', + name: 'amqp.content-type', + type: 'keyword', + }, + 'amqp.content-encoding': { + category: 'amqp', + description: 'MIME content encoding. ', + name: 'amqp.content-encoding', + type: 'keyword', + }, + 'amqp.headers': { + category: 'amqp', + description: 'Message header field table. ', + name: 'amqp.headers', + type: 'object', + }, + 'amqp.delivery-mode': { + category: 'amqp', + description: 'Non-persistent (1) or persistent (2). ', + name: 'amqp.delivery-mode', + type: 'keyword', + }, + 'amqp.priority': { + category: 'amqp', + description: 'Message priority, 0 to 9. ', + name: 'amqp.priority', + type: 'long', + }, + 'amqp.correlation-id': { + category: 'amqp', + description: 'Application correlation identifier. ', + name: 'amqp.correlation-id', + type: 'keyword', + }, + 'amqp.reply-to': { + category: 'amqp', + description: 'Address to reply to. ', + name: 'amqp.reply-to', + type: 'keyword', + }, + 'amqp.expiration': { + category: 'amqp', + description: 'Message expiration specification. ', + name: 'amqp.expiration', + type: 'keyword', + }, + 'amqp.message-id': { + category: 'amqp', + description: 'Application message identifier. ', + name: 'amqp.message-id', + type: 'keyword', + }, + 'amqp.timestamp': { + category: 'amqp', + description: 'Message timestamp. ', + name: 'amqp.timestamp', + type: 'keyword', + }, + 'amqp.type': { + category: 'amqp', + description: 'Message type name. ', + name: 'amqp.type', + type: 'keyword', + }, + 'amqp.user-id': { + category: 'amqp', + description: 'Creating user id. ', + name: 'amqp.user-id', + type: 'keyword', + }, + 'amqp.app-id': { + category: 'amqp', + description: 'Creating application id. ', + name: 'amqp.app-id', + type: 'keyword', + }, + no_request: { + category: 'base', + name: 'no_request', + type: 'alias', + }, + 'cassandra.no_request': { + category: 'cassandra', + description: 'Indicates that there is no request because this is a PUSH message. ', + name: 'cassandra.no_request', + type: 'boolean', + }, + 'cassandra.request.headers.version': { + category: 'cassandra', + description: 'The version of the protocol.', + name: 'cassandra.request.headers.version', + type: 'long', + }, + 'cassandra.request.headers.flags': { + category: 'cassandra', + description: 'Flags applying to this frame.', + name: 'cassandra.request.headers.flags', + type: 'keyword', + }, + 'cassandra.request.headers.stream': { + category: 'cassandra', + description: + 'A frame has a stream id. If a client sends a request message with the stream id X, it is guaranteed that the stream id of the response to that message will be X.', + name: 'cassandra.request.headers.stream', + type: 'keyword', + }, + 'cassandra.request.headers.op': { + category: 'cassandra', + description: 'An operation type that distinguishes the actual message.', + name: 'cassandra.request.headers.op', + type: 'keyword', + }, + 'cassandra.request.headers.length': { + category: 'cassandra', + description: + 'A integer representing the length of the body of the frame (a frame is limited to 256MB in length).', + name: 'cassandra.request.headers.length', + type: 'long', + }, + 'cassandra.request.query': { + category: 'cassandra', + description: 'The CQL query which client send to cassandra.', + name: 'cassandra.request.query', + type: 'keyword', + }, + 'cassandra.response.headers.version': { + category: 'cassandra', + description: 'The version of the protocol.', + name: 'cassandra.response.headers.version', + type: 'long', + }, + 'cassandra.response.headers.flags': { + category: 'cassandra', + description: 'Flags applying to this frame.', + name: 'cassandra.response.headers.flags', + type: 'keyword', + }, + 'cassandra.response.headers.stream': { + category: 'cassandra', + description: + 'A frame has a stream id. If a client sends a request message with the stream id X, it is guaranteed that the stream id of the response to that message will be X.', + name: 'cassandra.response.headers.stream', + type: 'keyword', + }, + 'cassandra.response.headers.op': { + category: 'cassandra', + description: 'An operation type that distinguishes the actual message.', + name: 'cassandra.response.headers.op', + type: 'keyword', + }, + 'cassandra.response.headers.length': { + category: 'cassandra', + description: + 'A integer representing the length of the body of the frame (a frame is limited to 256MB in length).', + name: 'cassandra.response.headers.length', + type: 'long', + }, + 'cassandra.response.result.type': { + category: 'cassandra', + description: 'Cassandra result type.', + name: 'cassandra.response.result.type', + type: 'keyword', + }, + 'cassandra.response.result.rows.num_rows': { + category: 'cassandra', + description: 'Representing the number of rows present in this result.', + name: 'cassandra.response.result.rows.num_rows', + type: 'long', + }, + 'cassandra.response.result.rows.meta.keyspace': { + category: 'cassandra', + description: 'Only present after set Global_tables_spec, the keyspace name.', + name: 'cassandra.response.result.rows.meta.keyspace', + type: 'keyword', + }, + 'cassandra.response.result.rows.meta.table': { + category: 'cassandra', + description: 'Only present after set Global_tables_spec, the table name.', + name: 'cassandra.response.result.rows.meta.table', + type: 'keyword', + }, + 'cassandra.response.result.rows.meta.flags': { + category: 'cassandra', + description: 'Provides information on the formatting of the remaining information.', + name: 'cassandra.response.result.rows.meta.flags', + type: 'keyword', + }, + 'cassandra.response.result.rows.meta.col_count': { + category: 'cassandra', + description: + 'Representing the number of columns selected by the query that produced this result.', + name: 'cassandra.response.result.rows.meta.col_count', + type: 'long', + }, + 'cassandra.response.result.rows.meta.pkey_columns': { + category: 'cassandra', + description: 'Representing the PK columns index and counts.', + name: 'cassandra.response.result.rows.meta.pkey_columns', + type: 'long', + }, + 'cassandra.response.result.rows.meta.paging_state': { + category: 'cassandra', + description: + 'The paging_state is a bytes value that should be used in QUERY/EXECUTE to continue paging and retrieve the remainder of the result for this query.', + name: 'cassandra.response.result.rows.meta.paging_state', + type: 'keyword', + }, + 'cassandra.response.result.keyspace': { + category: 'cassandra', + description: 'Indicating the name of the keyspace that has been set.', + name: 'cassandra.response.result.keyspace', + type: 'keyword', + }, + 'cassandra.response.result.schema_change.change': { + category: 'cassandra', + description: 'Representing the type of changed involved.', + name: 'cassandra.response.result.schema_change.change', + type: 'keyword', + }, + 'cassandra.response.result.schema_change.keyspace': { + category: 'cassandra', + description: 'This describes which keyspace has changed.', + name: 'cassandra.response.result.schema_change.keyspace', + type: 'keyword', + }, + 'cassandra.response.result.schema_change.table': { + category: 'cassandra', + description: 'This describes which table has changed.', + name: 'cassandra.response.result.schema_change.table', + type: 'keyword', + }, + 'cassandra.response.result.schema_change.object': { + category: 'cassandra', + description: + 'This describes the name of said affected object (either the table, user type, function, or aggregate name).', + name: 'cassandra.response.result.schema_change.object', + type: 'keyword', + }, + 'cassandra.response.result.schema_change.target': { + category: 'cassandra', + description: 'Target could be "FUNCTION" or "AGGREGATE", multiple arguments.', + name: 'cassandra.response.result.schema_change.target', + type: 'keyword', + }, + 'cassandra.response.result.schema_change.name': { + category: 'cassandra', + description: 'The function/aggregate name.', + name: 'cassandra.response.result.schema_change.name', + type: 'keyword', + }, + 'cassandra.response.result.schema_change.args': { + category: 'cassandra', + description: 'One string for each argument type (as CQL type).', + name: 'cassandra.response.result.schema_change.args', + type: 'keyword', + }, + 'cassandra.response.result.prepared.prepared_id': { + category: 'cassandra', + description: 'Representing the prepared query ID.', + name: 'cassandra.response.result.prepared.prepared_id', + type: 'keyword', + }, + 'cassandra.response.result.prepared.req_meta.keyspace': { + category: 'cassandra', + description: 'Only present after set Global_tables_spec, the keyspace name.', + name: 'cassandra.response.result.prepared.req_meta.keyspace', + type: 'keyword', + }, + 'cassandra.response.result.prepared.req_meta.table': { + category: 'cassandra', + description: 'Only present after set Global_tables_spec, the table name.', + name: 'cassandra.response.result.prepared.req_meta.table', + type: 'keyword', + }, + 'cassandra.response.result.prepared.req_meta.flags': { + category: 'cassandra', + description: 'Provides information on the formatting of the remaining information.', + name: 'cassandra.response.result.prepared.req_meta.flags', + type: 'keyword', + }, + 'cassandra.response.result.prepared.req_meta.col_count': { + category: 'cassandra', + description: + 'Representing the number of columns selected by the query that produced this result.', + name: 'cassandra.response.result.prepared.req_meta.col_count', + type: 'long', + }, + 'cassandra.response.result.prepared.req_meta.pkey_columns': { + category: 'cassandra', + description: 'Representing the PK columns index and counts.', + name: 'cassandra.response.result.prepared.req_meta.pkey_columns', + type: 'long', + }, + 'cassandra.response.result.prepared.req_meta.paging_state': { + category: 'cassandra', + description: + 'The paging_state is a bytes value that should be used in QUERY/EXECUTE to continue paging and retrieve the remainder of the result for this query.', + name: 'cassandra.response.result.prepared.req_meta.paging_state', + type: 'keyword', + }, + 'cassandra.response.result.prepared.resp_meta.keyspace': { + category: 'cassandra', + description: 'Only present after set Global_tables_spec, the keyspace name.', + name: 'cassandra.response.result.prepared.resp_meta.keyspace', + type: 'keyword', + }, + 'cassandra.response.result.prepared.resp_meta.table': { + category: 'cassandra', + description: 'Only present after set Global_tables_spec, the table name.', + name: 'cassandra.response.result.prepared.resp_meta.table', + type: 'keyword', + }, + 'cassandra.response.result.prepared.resp_meta.flags': { + category: 'cassandra', + description: 'Provides information on the formatting of the remaining information.', + name: 'cassandra.response.result.prepared.resp_meta.flags', + type: 'keyword', + }, + 'cassandra.response.result.prepared.resp_meta.col_count': { + category: 'cassandra', + description: + 'Representing the number of columns selected by the query that produced this result.', + name: 'cassandra.response.result.prepared.resp_meta.col_count', + type: 'long', + }, + 'cassandra.response.result.prepared.resp_meta.pkey_columns': { + category: 'cassandra', + description: 'Representing the PK columns index and counts.', + name: 'cassandra.response.result.prepared.resp_meta.pkey_columns', + type: 'long', + }, + 'cassandra.response.result.prepared.resp_meta.paging_state': { + category: 'cassandra', + description: + 'The paging_state is a bytes value that should be used in QUERY/EXECUTE to continue paging and retrieve the remainder of the result for this query.', + name: 'cassandra.response.result.prepared.resp_meta.paging_state', + type: 'keyword', + }, + 'cassandra.response.supported': { + category: 'cassandra', + description: + 'Indicates which startup options are supported by the server. This message comes as a response to an OPTIONS message.', + name: 'cassandra.response.supported', + type: 'object', + }, + 'cassandra.response.authentication.class': { + category: 'cassandra', + description: 'Indicates the full class name of the IAuthenticator in use', + name: 'cassandra.response.authentication.class', + type: 'keyword', + }, + 'cassandra.response.warnings': { + category: 'cassandra', + description: 'The text of the warnings, only occur when Warning flag was set.', + name: 'cassandra.response.warnings', + type: 'keyword', + }, + 'cassandra.response.event.type': { + category: 'cassandra', + description: 'Representing the event type.', + name: 'cassandra.response.event.type', + type: 'keyword', + }, + 'cassandra.response.event.change': { + category: 'cassandra', + description: + 'The message corresponding respectively to the type of change followed by the address of the new/removed node.', + name: 'cassandra.response.event.change', + type: 'keyword', + }, + 'cassandra.response.event.host': { + category: 'cassandra', + description: 'Representing the node ip.', + name: 'cassandra.response.event.host', + type: 'keyword', + }, + 'cassandra.response.event.port': { + category: 'cassandra', + description: 'Representing the node port.', + name: 'cassandra.response.event.port', + type: 'long', + }, + 'cassandra.response.event.schema_change.change': { + category: 'cassandra', + description: 'Representing the type of changed involved.', + name: 'cassandra.response.event.schema_change.change', + type: 'keyword', + }, + 'cassandra.response.event.schema_change.keyspace': { + category: 'cassandra', + description: 'This describes which keyspace has changed.', + name: 'cassandra.response.event.schema_change.keyspace', + type: 'keyword', + }, + 'cassandra.response.event.schema_change.table': { + category: 'cassandra', + description: 'This describes which table has changed.', + name: 'cassandra.response.event.schema_change.table', + type: 'keyword', + }, + 'cassandra.response.event.schema_change.object': { + category: 'cassandra', + description: + 'This describes the name of said affected object (either the table, user type, function, or aggregate name).', + name: 'cassandra.response.event.schema_change.object', + type: 'keyword', + }, + 'cassandra.response.event.schema_change.target': { + category: 'cassandra', + description: 'Target could be "FUNCTION" or "AGGREGATE", multiple arguments.', + name: 'cassandra.response.event.schema_change.target', + type: 'keyword', + }, + 'cassandra.response.event.schema_change.name': { + category: 'cassandra', + description: 'The function/aggregate name.', + name: 'cassandra.response.event.schema_change.name', + type: 'keyword', + }, + 'cassandra.response.event.schema_change.args': { + category: 'cassandra', + description: 'One string for each argument type (as CQL type).', + name: 'cassandra.response.event.schema_change.args', + type: 'keyword', + }, + 'cassandra.response.error.code': { + category: 'cassandra', + description: 'The error code of the Cassandra response.', + name: 'cassandra.response.error.code', + type: 'long', + }, + 'cassandra.response.error.msg': { + category: 'cassandra', + description: 'The error message of the Cassandra response.', + name: 'cassandra.response.error.msg', + type: 'keyword', + }, + 'cassandra.response.error.type': { + category: 'cassandra', + description: 'The error type of the Cassandra response.', + name: 'cassandra.response.error.type', + type: 'keyword', + }, + 'cassandra.response.error.details.read_consistency': { + category: 'cassandra', + description: 'Representing the consistency level of the query that triggered the exception.', + name: 'cassandra.response.error.details.read_consistency', + type: 'keyword', + }, + 'cassandra.response.error.details.required': { + category: 'cassandra', + description: + 'Representing the number of nodes that should be alive to respect consistency level.', + name: 'cassandra.response.error.details.required', + type: 'long', + }, + 'cassandra.response.error.details.alive': { + category: 'cassandra', + description: + 'Representing the number of replicas that were known to be alive when the request had been processed (since an unavailable exception has been triggered).', + name: 'cassandra.response.error.details.alive', + type: 'long', + }, + 'cassandra.response.error.details.received': { + category: 'cassandra', + description: 'Representing the number of nodes having acknowledged the request.', + name: 'cassandra.response.error.details.received', + type: 'long', + }, + 'cassandra.response.error.details.blockfor': { + category: 'cassandra', + description: + 'Representing the number of replicas whose acknowledgement is required to achieve consistency level.', + name: 'cassandra.response.error.details.blockfor', + type: 'long', + }, + 'cassandra.response.error.details.write_type': { + category: 'cassandra', + description: 'Describe the type of the write that timed out.', + name: 'cassandra.response.error.details.write_type', + type: 'keyword', + }, + 'cassandra.response.error.details.data_present': { + category: 'cassandra', + description: 'It means the replica that was asked for data had responded.', + name: 'cassandra.response.error.details.data_present', + type: 'boolean', + }, + 'cassandra.response.error.details.keyspace': { + category: 'cassandra', + description: 'The keyspace of the failed function.', + name: 'cassandra.response.error.details.keyspace', + type: 'keyword', + }, + 'cassandra.response.error.details.table': { + category: 'cassandra', + description: 'The keyspace of the failed function.', + name: 'cassandra.response.error.details.table', + type: 'keyword', + }, + 'cassandra.response.error.details.stmt_id': { + category: 'cassandra', + description: 'Representing the unknown ID.', + name: 'cassandra.response.error.details.stmt_id', + type: 'keyword', + }, + 'cassandra.response.error.details.num_failures': { + category: 'cassandra', + description: + 'Representing the number of nodes that experience a failure while executing the request.', + name: 'cassandra.response.error.details.num_failures', + type: 'keyword', + }, + 'cassandra.response.error.details.function': { + category: 'cassandra', + description: 'The name of the failed function.', + name: 'cassandra.response.error.details.function', + type: 'keyword', + }, + 'cassandra.response.error.details.arg_types': { + category: 'cassandra', + description: 'One string for each argument type (as CQL type) of the failed function.', + name: 'cassandra.response.error.details.arg_types', + type: 'keyword', + }, + 'dhcpv4.transaction_id': { + category: 'dhcpv4', + description: + 'Transaction ID, a random number chosen by the client, used by the client and server to associate messages and responses between a client and a server. ', + name: 'dhcpv4.transaction_id', + type: 'keyword', + }, + 'dhcpv4.seconds': { + category: 'dhcpv4', + description: + 'Number of seconds elapsed since client began address acquisition or renewal process. ', + name: 'dhcpv4.seconds', + type: 'long', + }, + 'dhcpv4.flags': { + category: 'dhcpv4', + description: + 'Flags are set by the client to indicate how the DHCP server should its reply -- either unicast or broadcast. ', + name: 'dhcpv4.flags', + type: 'keyword', + }, + 'dhcpv4.client_ip': { + category: 'dhcpv4', + description: 'The current IP address of the client.', + name: 'dhcpv4.client_ip', + type: 'ip', + }, + 'dhcpv4.assigned_ip': { + category: 'dhcpv4', + description: + 'The IP address that the DHCP server is assigning to the client. This field is also known as "your" IP address. ', + name: 'dhcpv4.assigned_ip', + type: 'ip', + }, + 'dhcpv4.server_ip': { + category: 'dhcpv4', + description: + 'The IP address of the DHCP server that the client should use for the next step in the bootstrap process. ', + name: 'dhcpv4.server_ip', + type: 'ip', + }, + 'dhcpv4.relay_ip': { + category: 'dhcpv4', + description: + 'The relay IP address used by the client to contact the server (i.e. a DHCP relay server). ', + name: 'dhcpv4.relay_ip', + type: 'ip', + }, + 'dhcpv4.client_mac': { + category: 'dhcpv4', + description: "The client's MAC address (layer two).", + name: 'dhcpv4.client_mac', + type: 'keyword', + }, + 'dhcpv4.server_name': { + category: 'dhcpv4', + description: + 'The name of the server sending the message. Optional. Used in DHCPOFFER or DHCPACK messages. ', + name: 'dhcpv4.server_name', + type: 'keyword', + }, + 'dhcpv4.op_code': { + category: 'dhcpv4', + description: 'The message op code (bootrequest or bootreply). ', + example: 'bootreply', + name: 'dhcpv4.op_code', + type: 'keyword', + }, + 'dhcpv4.hops': { + category: 'dhcpv4', + description: 'The number of hops the DHCP message went through.', + name: 'dhcpv4.hops', + type: 'long', + }, + 'dhcpv4.hardware_type': { + category: 'dhcpv4', + description: 'The type of hardware used for the local network (Ethernet, LocalTalk, etc). ', + name: 'dhcpv4.hardware_type', + type: 'keyword', + }, + 'dhcpv4.option.message_type': { + category: 'dhcpv4', + description: + 'The specific type of DHCP message being sent (e.g. discover, offer, request, decline, ack, nak, release, inform). ', + example: 'ack', + name: 'dhcpv4.option.message_type', + type: 'keyword', + }, + 'dhcpv4.option.parameter_request_list': { + category: 'dhcpv4', + description: + 'This option is used by a DHCP client to request values for specified configuration parameters. ', + name: 'dhcpv4.option.parameter_request_list', + type: 'keyword', + }, + 'dhcpv4.option.requested_ip_address': { + category: 'dhcpv4', + description: + 'This option is used in a client request (DHCPDISCOVER) to allow the client to request that a particular IP address be assigned. ', + name: 'dhcpv4.option.requested_ip_address', + type: 'ip', + }, + 'dhcpv4.option.server_identifier': { + category: 'dhcpv4', + description: 'IP address of the individual DHCP server which handled this message. ', + name: 'dhcpv4.option.server_identifier', + type: 'ip', + }, + 'dhcpv4.option.broadcast_address': { + category: 'dhcpv4', + description: "This option specifies the broadcast address in use on the client's subnet. ", + name: 'dhcpv4.option.broadcast_address', + type: 'ip', + }, + 'dhcpv4.option.max_dhcp_message_size': { + category: 'dhcpv4', + description: + 'This option specifies the maximum length DHCP message that the client is willing to accept. ', + name: 'dhcpv4.option.max_dhcp_message_size', + type: 'long', + }, + 'dhcpv4.option.class_identifier': { + category: 'dhcpv4', + description: + "This option is used by DHCP clients to optionally identify the vendor type and configuration of a DHCP client. Vendors may choose to define specific vendor class identifiers to convey particular configuration or other identification information about a client. For example, the identifier may encode the client's hardware configuration. ", + name: 'dhcpv4.option.class_identifier', + type: 'keyword', + }, + 'dhcpv4.option.domain_name': { + category: 'dhcpv4', + description: + 'This option specifies the domain name that client should use when resolving hostnames via the Domain Name System. ', + name: 'dhcpv4.option.domain_name', + type: 'keyword', + }, + 'dhcpv4.option.dns_servers': { + category: 'dhcpv4', + description: + 'The domain name server option specifies a list of Domain Name System servers available to the client. ', + name: 'dhcpv4.option.dns_servers', + type: 'ip', + }, + 'dhcpv4.option.vendor_identifying_options': { + category: 'dhcpv4', + description: + 'A DHCP client may use this option to unambiguously identify the vendor that manufactured the hardware on which the client is running, the software in use, or an industry consortium to which the vendor belongs. This field is described in RFC 3925. ', + name: 'dhcpv4.option.vendor_identifying_options', + type: 'object', + }, + 'dhcpv4.option.subnet_mask': { + category: 'dhcpv4', + description: 'The subnet mask that the client should use on the currnet network. ', + name: 'dhcpv4.option.subnet_mask', + type: 'ip', + }, + 'dhcpv4.option.utc_time_offset_sec': { + category: 'dhcpv4', + description: + "The time offset field specifies the offset of the client's subnet in seconds from Coordinated Universal Time (UTC). ", + name: 'dhcpv4.option.utc_time_offset_sec', + type: 'long', + }, + 'dhcpv4.option.router': { + category: 'dhcpv4', + description: + "The router option specifies a list of IP addresses for routers on the client's subnet. ", + name: 'dhcpv4.option.router', + type: 'ip', + }, + 'dhcpv4.option.time_servers': { + category: 'dhcpv4', + description: + 'The time server option specifies a list of RFC 868 time servers available to the client. ', + name: 'dhcpv4.option.time_servers', + type: 'ip', + }, + 'dhcpv4.option.ntp_servers': { + category: 'dhcpv4', + description: + 'This option specifies a list of IP addresses indicating NTP servers available to the client. ', + name: 'dhcpv4.option.ntp_servers', + type: 'ip', + }, + 'dhcpv4.option.hostname': { + category: 'dhcpv4', + description: 'This option specifies the name of the client. ', + name: 'dhcpv4.option.hostname', + type: 'keyword', + }, + 'dhcpv4.option.ip_address_lease_time_sec': { + category: 'dhcpv4', + description: + 'This option is used in a client request (DHCPDISCOVER or DHCPREQUEST) to allow the client to request a lease time for the IP address. In a server reply (DHCPOFFER), a DHCP server uses this option to specify the lease time it is willing to offer. ', + name: 'dhcpv4.option.ip_address_lease_time_sec', + type: 'long', + }, + 'dhcpv4.option.message': { + category: 'dhcpv4', + description: + 'This option is used by a DHCP server to provide an error message to a DHCP client in a DHCPNAK message in the event of a failure. A client may use this option in a DHCPDECLINE message to indicate the why the client declined the offered parameters. ', + name: 'dhcpv4.option.message', + type: 'text', + }, + 'dhcpv4.option.renewal_time_sec': { + category: 'dhcpv4', + description: + 'This option specifies the time interval from address assignment until the client transitions to the RENEWING state. ', + name: 'dhcpv4.option.renewal_time_sec', + type: 'long', + }, + 'dhcpv4.option.rebinding_time_sec': { + category: 'dhcpv4', + description: + 'This option specifies the time interval from address assignment until the client transitions to the REBINDING state. ', + name: 'dhcpv4.option.rebinding_time_sec', + type: 'long', + }, + 'dhcpv4.option.boot_file_name': { + category: 'dhcpv4', + description: + "This option is used to identify a bootfile when the 'file' field in the DHCP header has been used for DHCP options. ", + name: 'dhcpv4.option.boot_file_name', + type: 'keyword', + }, + 'dns.flags.authoritative': { + category: 'dns', + description: + 'A DNS flag specifying that the responding server is an authority for the domain name used in the question. ', + name: 'dns.flags.authoritative', + type: 'boolean', + }, + 'dns.flags.recursion_available': { + category: 'dns', + description: + 'A DNS flag specifying whether recursive query support is available in the name server. ', + name: 'dns.flags.recursion_available', + type: 'boolean', + }, + 'dns.flags.recursion_desired': { + category: 'dns', + description: + 'A DNS flag specifying that the client directs the server to pursue a query recursively. Recursive query support is optional. ', + name: 'dns.flags.recursion_desired', + type: 'boolean', + }, + 'dns.flags.authentic_data': { + category: 'dns', + description: + 'A DNS flag specifying that the recursive server considers the response authentic. ', + name: 'dns.flags.authentic_data', + type: 'boolean', + }, + 'dns.flags.checking_disabled': { + category: 'dns', + description: + 'A DNS flag specifying that the client disables the server signature validation of the query. ', + name: 'dns.flags.checking_disabled', + type: 'boolean', + }, + 'dns.flags.truncated_response': { + category: 'dns', + description: 'A DNS flag specifying that only the first 512 bytes of the reply were returned. ', + name: 'dns.flags.truncated_response', + type: 'boolean', + }, + 'dns.question.etld_plus_one': { + category: 'dns', + description: + 'The effective top-level domain (eTLD) plus one more label. For example, the eTLD+1 for "foo.bar.golang.org." is "golang.org.". The data for determining the eTLD comes from an embedded copy of the data from http://publicsuffix.org.', + example: 'amazon.co.uk.', + name: 'dns.question.etld_plus_one', + }, + 'dns.answers_count': { + category: 'dns', + description: 'The number of resource records contained in the `dns.answers` field. ', + name: 'dns.answers_count', + type: 'long', + }, + 'dns.authorities': { + category: 'dns', + description: 'An array containing a dictionary for each authority section from the answer. ', + name: 'dns.authorities', + type: 'object', + }, + 'dns.authorities_count': { + category: 'dns', + description: + 'The number of resource records contained in the `dns.authorities` field. The `dns.authorities` field may or may not be included depending on the configuration of Packetbeat. ', + name: 'dns.authorities_count', + type: 'long', + }, + 'dns.authorities.name': { + category: 'dns', + description: 'The domain name to which this resource record pertains.', + example: 'example.com.', + name: 'dns.authorities.name', + }, + 'dns.authorities.type': { + category: 'dns', + description: 'The type of data contained in this resource record.', + example: 'NS', + name: 'dns.authorities.type', + }, + 'dns.authorities.class': { + category: 'dns', + description: 'The class of DNS data contained in this resource record.', + example: 'IN', + name: 'dns.authorities.class', + }, + 'dns.additionals': { + category: 'dns', + description: 'An array containing a dictionary for each additional section from the answer. ', + name: 'dns.additionals', + type: 'object', + }, + 'dns.additionals_count': { + category: 'dns', + description: + 'The number of resource records contained in the `dns.additionals` field. The `dns.additionals` field may or may not be included depending on the configuration of Packetbeat. ', + name: 'dns.additionals_count', + type: 'long', + }, + 'dns.additionals.name': { + category: 'dns', + description: 'The domain name to which this resource record pertains.', + example: 'example.com.', + name: 'dns.additionals.name', + }, + 'dns.additionals.type': { + category: 'dns', + description: 'The type of data contained in this resource record.', + example: 'NS', + name: 'dns.additionals.type', + }, + 'dns.additionals.class': { + category: 'dns', + description: 'The class of DNS data contained in this resource record.', + example: 'IN', + name: 'dns.additionals.class', + }, + 'dns.additionals.ttl': { + category: 'dns', + description: + 'The time interval in seconds that this resource record may be cached before it should be discarded. Zero values mean that the data should not be cached. ', + name: 'dns.additionals.ttl', + type: 'long', + }, + 'dns.additionals.data': { + category: 'dns', + description: + 'The data describing the resource. The meaning of this data depends on the type and class of the resource record. ', + name: 'dns.additionals.data', + }, + 'dns.opt.version': { + category: 'dns', + description: 'The EDNS version.', + example: '0', + name: 'dns.opt.version', + }, + 'dns.opt.do': { + category: 'dns', + description: 'If set, the transaction uses DNSSEC.', + name: 'dns.opt.do', + type: 'boolean', + }, + 'dns.opt.ext_rcode': { + category: 'dns', + description: 'Extended response code field.', + example: 'BADVERS', + name: 'dns.opt.ext_rcode', + }, + 'dns.opt.udp_size': { + category: 'dns', + description: "Requestor's UDP payload size (in bytes).", + name: 'dns.opt.udp_size', + type: 'long', + }, + 'http.request.headers': { + category: 'http', + description: + 'A map containing the captured header fields from the request. Which headers to capture is configurable. If headers with the same header name are present in the message, they will be separated by commas. ', + name: 'http.request.headers', + type: 'object', + }, + 'http.request.params': { + category: 'http', + name: 'http.request.params', + type: 'alias', + }, + 'http.response.status_phrase': { + category: 'http', + description: 'The HTTP status phrase.', + example: 'Not Found', + name: 'http.response.status_phrase', + }, + 'http.response.headers': { + category: 'http', + description: + 'A map containing the captured header fields from the response. Which headers to capture is configurable. If headers with the same header name are present in the message, they will be separated by commas. ', + name: 'http.response.headers', + type: 'object', + }, + 'http.response.code': { + category: 'http', + name: 'http.response.code', + type: 'alias', + }, + 'http.response.phrase': { + category: 'http', + name: 'http.response.phrase', + type: 'alias', + }, + 'icmp.version': { + category: 'icmp', + description: 'The version of the ICMP protocol.', + name: 'icmp.version', + }, + 'icmp.request.message': { + category: 'icmp', + description: 'A human readable form of the request.', + name: 'icmp.request.message', + type: 'keyword', + }, + 'icmp.request.type': { + category: 'icmp', + description: 'The request type.', + name: 'icmp.request.type', + type: 'long', + }, + 'icmp.request.code': { + category: 'icmp', + description: 'The request code.', + name: 'icmp.request.code', + type: 'long', + }, + 'icmp.response.message': { + category: 'icmp', + description: 'A human readable form of the response.', + name: 'icmp.response.message', + type: 'keyword', + }, + 'icmp.response.type': { + category: 'icmp', + description: 'The response type.', + name: 'icmp.response.type', + type: 'long', + }, + 'icmp.response.code': { + category: 'icmp', + description: 'The response code.', + name: 'icmp.response.code', + type: 'long', + }, + 'memcache.protocol_type': { + category: 'memcache', + description: + 'The memcache protocol implementation. The value can be "binary" for binary-based, "text" for text-based, or "unknown" for an unknown memcache protocol type. ', + name: 'memcache.protocol_type', + type: 'keyword', + }, + 'memcache.request.line': { + category: 'memcache', + description: 'The raw command line for unknown commands ONLY. ', + name: 'memcache.request.line', + type: 'keyword', + }, + 'memcache.request.command': { + category: 'memcache', + description: + 'The memcache command being requested in the memcache text protocol. For example "set" or "get". The binary protocol opcodes are translated into memcache text protocol commands. ', + name: 'memcache.request.command', + type: 'keyword', + }, + 'memcache.response.command': { + category: 'memcache', + description: + 'Either the text based protocol response message type or the name of the originating request if binary protocol is used. ', + name: 'memcache.response.command', + type: 'keyword', + }, + 'memcache.request.type': { + category: 'memcache', + description: + 'The memcache command classification. This value can be "UNKNOWN", "Load", "Store", "Delete", "Counter", "Info", "SlabCtrl", "LRUCrawler", "Stats", "Success", "Fail", or "Auth". ', + name: 'memcache.request.type', + type: 'keyword', + }, + 'memcache.response.type': { + category: 'memcache', + description: + 'The memcache command classification. This value can be "UNKNOWN", "Load", "Store", "Delete", "Counter", "Info", "SlabCtrl", "LRUCrawler", "Stats", "Success", "Fail", or "Auth". The text based protocol will employ any of these, whereas the binary based protocol will mirror the request commands only (see `memcache.response.status` for binary protocol). ', + name: 'memcache.response.type', + type: 'keyword', + }, + 'memcache.response.error_msg': { + category: 'memcache', + description: 'The optional error message in the memcache response (text based protocol only). ', + name: 'memcache.response.error_msg', + type: 'keyword', + }, + 'memcache.request.opcode': { + category: 'memcache', + description: 'The binary protocol message opcode name. ', + name: 'memcache.request.opcode', + type: 'keyword', + }, + 'memcache.response.opcode': { + category: 'memcache', + description: 'The binary protocol message opcode name. ', + name: 'memcache.response.opcode', + type: 'keyword', + }, + 'memcache.request.opcode_value': { + category: 'memcache', + description: 'The binary protocol message opcode value. ', + name: 'memcache.request.opcode_value', + type: 'long', + }, + 'memcache.response.opcode_value': { + category: 'memcache', + description: 'The binary protocol message opcode value. ', + name: 'memcache.response.opcode_value', + type: 'long', + }, + 'memcache.request.opaque': { + category: 'memcache', + description: + 'The binary protocol opaque header value used for correlating request with response messages. ', + name: 'memcache.request.opaque', + type: 'long', + }, + 'memcache.response.opaque': { + category: 'memcache', + description: + 'The binary protocol opaque header value used for correlating request with response messages. ', + name: 'memcache.response.opaque', + type: 'long', + }, + 'memcache.request.vbucket': { + category: 'memcache', + description: 'The vbucket index sent in the binary message. ', + name: 'memcache.request.vbucket', + type: 'long', + }, + 'memcache.response.status': { + category: 'memcache', + description: 'The textual representation of the response error code (binary protocol only). ', + name: 'memcache.response.status', + type: 'keyword', + }, + 'memcache.response.status_code': { + category: 'memcache', + description: 'The status code value returned in the response (binary protocol only). ', + name: 'memcache.response.status_code', + type: 'long', + }, + 'memcache.request.keys': { + category: 'memcache', + description: 'The list of keys sent in the store or load commands. ', + name: 'memcache.request.keys', + type: 'array', + }, + 'memcache.response.keys': { + category: 'memcache', + description: 'The list of keys returned for the load command (if present). ', + name: 'memcache.response.keys', + type: 'array', + }, + 'memcache.request.count_values': { + category: 'memcache', + description: + 'The number of values found in the memcache request message. If the command does not send any data, this field is missing. ', + name: 'memcache.request.count_values', + type: 'long', + }, + 'memcache.response.count_values': { + category: 'memcache', + description: + 'The number of values found in the memcache response message. If the command does not send any data, this field is missing. ', + name: 'memcache.response.count_values', + type: 'long', + }, + 'memcache.request.values': { + category: 'memcache', + description: 'The list of base64 encoded values sent with the request (if present). ', + name: 'memcache.request.values', + type: 'array', + }, + 'memcache.response.values': { + category: 'memcache', + description: 'The list of base64 encoded values sent with the response (if present). ', + name: 'memcache.response.values', + type: 'array', + }, + 'memcache.request.bytes': { + category: 'memcache', + description: 'The byte count of the values being transferred. ', + name: 'memcache.request.bytes', + type: 'long', + format: 'bytes', + }, + 'memcache.response.bytes': { + category: 'memcache', + description: 'The byte count of the values being transferred. ', + name: 'memcache.response.bytes', + type: 'long', + format: 'bytes', + }, + 'memcache.request.delta': { + category: 'memcache', + description: 'The counter increment/decrement delta value. ', + name: 'memcache.request.delta', + type: 'long', + }, + 'memcache.request.initial': { + category: 'memcache', + description: 'The counter increment/decrement initial value parameter (binary protocol only). ', + name: 'memcache.request.initial', + type: 'long', + }, + 'memcache.request.verbosity': { + category: 'memcache', + description: 'The value of the memcache "verbosity" command. ', + name: 'memcache.request.verbosity', + type: 'long', + }, + 'memcache.request.raw_args': { + category: 'memcache', + description: + 'The text protocol raw arguments for the "stats ..." and "lru crawl ..." commands. ', + name: 'memcache.request.raw_args', + type: 'keyword', + }, + 'memcache.request.source_class': { + category: 'memcache', + description: "The source class id in 'slab reassign' command. ", + name: 'memcache.request.source_class', + type: 'long', + }, + 'memcache.request.dest_class': { + category: 'memcache', + description: "The destination class id in 'slab reassign' command. ", + name: 'memcache.request.dest_class', + type: 'long', + }, + 'memcache.request.automove': { + category: 'memcache', + description: + 'The automove mode in the \'slab automove\' command expressed as a string. This value can be "standby"(=0), "slow"(=1), "aggressive"(=2), or the raw value if the value is unknown. ', + name: 'memcache.request.automove', + type: 'keyword', + }, + 'memcache.request.flags': { + category: 'memcache', + description: 'The memcache command flags sent in the request (if present). ', + name: 'memcache.request.flags', + type: 'long', + }, + 'memcache.response.flags': { + category: 'memcache', + description: 'The memcache message flags sent in the response (if present). ', + name: 'memcache.response.flags', + type: 'long', + }, + 'memcache.request.exptime': { + category: 'memcache', + description: + 'The data expiry time in seconds sent with the memcache command (if present). If the value is <30 days, the expiry time is relative to "now", or else it is an absolute Unix time in seconds (32-bit). ', + name: 'memcache.request.exptime', + type: 'long', + }, + 'memcache.request.sleep_us': { + category: 'memcache', + description: "The sleep setting in microseconds for the 'lru_crawler sleep' command. ", + name: 'memcache.request.sleep_us', + type: 'long', + }, + 'memcache.response.value': { + category: 'memcache', + description: 'The counter value returned by a counter operation. ', + name: 'memcache.response.value', + type: 'long', + }, + 'memcache.request.noreply': { + category: 'memcache', + description: + 'Set to true if noreply was set in the request. The `memcache.response` field will be missing. ', + name: 'memcache.request.noreply', + type: 'boolean', + }, + 'memcache.request.quiet': { + category: 'memcache', + description: 'Set to true if the binary protocol message is to be treated as a quiet message. ', + name: 'memcache.request.quiet', + type: 'boolean', + }, + 'memcache.request.cas_unique': { + category: 'memcache', + description: 'The CAS (compare-and-swap) identifier if present. ', + name: 'memcache.request.cas_unique', + type: 'long', + }, + 'memcache.response.cas_unique': { + category: 'memcache', + description: + 'The CAS (compare-and-swap) identifier to be used with CAS-based updates (if present). ', + name: 'memcache.response.cas_unique', + type: 'long', + }, + 'memcache.response.stats': { + category: 'memcache', + description: + 'The list of statistic values returned. Each entry is a dictionary with the fields "name" and "value". ', + name: 'memcache.response.stats', + type: 'array', + }, + 'memcache.response.version': { + category: 'memcache', + description: 'The returned memcache version string. ', + name: 'memcache.response.version', + type: 'keyword', + }, + 'mongodb.error': { + category: 'mongodb', + description: + 'If the MongoDB request has resulted in an error, this field contains the error message returned by the server. ', + name: 'mongodb.error', + }, + 'mongodb.fullCollectionName': { + category: 'mongodb', + description: + 'The full collection name. The full collection name is the concatenation of the database name with the collection name, using a dot (.) for the concatenation. For example, for the database foo and the collection bar, the full collection name is foo.bar. ', + name: 'mongodb.fullCollectionName', + }, + 'mongodb.numberToSkip': { + category: 'mongodb', + description: + 'Sets the number of documents to omit - starting from the first document in the resulting dataset - when returning the result of the query. ', + name: 'mongodb.numberToSkip', + type: 'long', + }, + 'mongodb.numberToReturn': { + category: 'mongodb', + description: 'The requested maximum number of documents to be returned. ', + name: 'mongodb.numberToReturn', + type: 'long', + }, + 'mongodb.numberReturned': { + category: 'mongodb', + description: 'The number of documents in the reply. ', + name: 'mongodb.numberReturned', + type: 'long', + }, + 'mongodb.startingFrom': { + category: 'mongodb', + description: 'Where in the cursor this reply is starting. ', + name: 'mongodb.startingFrom', + }, + 'mongodb.query': { + category: 'mongodb', + description: + 'A JSON document that represents the query. The query will contain one or more elements, all of which must match for a document to be included in the result set. Possible elements include $query, $orderby, $hint, $explain, and $snapshot. ', + name: 'mongodb.query', + }, + 'mongodb.returnFieldsSelector': { + category: 'mongodb', + description: + 'A JSON document that limits the fields in the returned documents. The returnFieldsSelector contains one or more elements, each of which is the name of a field that should be returned, and the integer value 1. ', + name: 'mongodb.returnFieldsSelector', + }, + 'mongodb.selector': { + category: 'mongodb', + description: + 'A BSON document that specifies the query for selecting the document to update or delete. ', + name: 'mongodb.selector', + }, + 'mongodb.update': { + category: 'mongodb', + description: + 'A BSON document that specifies the update to be performed. For information on specifying updates, see the Update Operations documentation from the MongoDB Manual. ', + name: 'mongodb.update', + }, + 'mongodb.cursorId': { + category: 'mongodb', + description: + 'The cursor identifier returned in the OP_REPLY. This must be the value that was returned from the database. ', + name: 'mongodb.cursorId', + }, + 'mysql.affected_rows': { + category: 'mysql', + description: + 'If the MySQL command is successful, this field contains the affected number of rows of the last statement. ', + name: 'mysql.affected_rows', + type: 'long', + }, + 'mysql.insert_id': { + category: 'mysql', + description: + 'If the INSERT query is successful, this field contains the id of the newly inserted row. ', + name: 'mysql.insert_id', + }, + 'mysql.num_fields': { + category: 'mysql', + description: + 'If the SELECT query is successful, this field is set to the number of fields returned. ', + name: 'mysql.num_fields', + }, + 'mysql.num_rows': { + category: 'mysql', + description: + 'If the SELECT query is successful, this field is set to the number of rows returned. ', + name: 'mysql.num_rows', + }, + 'mysql.query': { + category: 'mysql', + description: "The row mysql query as read from the transaction's request. ", + name: 'mysql.query', + }, + 'mysql.error_code': { + category: 'mysql', + description: 'The error code returned by MySQL. ', + name: 'mysql.error_code', + type: 'long', + }, + 'mysql.error_message': { + category: 'mysql', + description: 'The error info message returned by MySQL. ', + name: 'mysql.error_message', + }, + 'nfs.version': { + category: 'nfs', + description: 'NFS protocol version number.', + name: 'nfs.version', + type: 'long', + }, + 'nfs.minor_version': { + category: 'nfs', + description: 'NFS protocol minor version number.', + name: 'nfs.minor_version', + type: 'long', + }, + 'nfs.tag': { + category: 'nfs', + description: 'NFS v4 COMPOUND operation tag.', + name: 'nfs.tag', + }, + 'nfs.opcode': { + category: 'nfs', + description: 'NFS operation name, or main operation name, in case of COMPOUND calls. ', + name: 'nfs.opcode', + }, + 'nfs.status': { + category: 'nfs', + description: 'NFS operation reply status.', + name: 'nfs.status', + }, + 'rpc.xid': { + category: 'rpc', + description: 'RPC message transaction identifier.', + name: 'rpc.xid', + }, + 'rpc.status': { + category: 'rpc', + description: 'RPC message reply status.', + name: 'rpc.status', + }, + 'rpc.auth_flavor': { + category: 'rpc', + description: 'RPC authentication flavor.', + name: 'rpc.auth_flavor', + }, + 'rpc.cred.uid': { + category: 'rpc', + description: "RPC caller's user id, in case of auth-unix.", + name: 'rpc.cred.uid', + type: 'long', + }, + 'rpc.cred.gid': { + category: 'rpc', + description: "RPC caller's group id, in case of auth-unix.", + name: 'rpc.cred.gid', + type: 'long', + }, + 'rpc.cred.gids': { + category: 'rpc', + description: "RPC caller's secondary group ids, in case of auth-unix.", + name: 'rpc.cred.gids', + }, + 'rpc.cred.stamp': { + category: 'rpc', + description: 'Arbitrary ID which the caller machine may generate.', + name: 'rpc.cred.stamp', + type: 'long', + }, + 'rpc.cred.machinename': { + category: 'rpc', + description: "The name of the caller's machine.", + name: 'rpc.cred.machinename', + }, + 'rpc.call_size': { + category: 'rpc', + description: 'RPC call size with argument.', + name: 'rpc.call_size', + type: 'alias', + }, + 'rpc.reply_size': { + category: 'rpc', + description: 'RPC reply size with argument.', + name: 'rpc.reply_size', + type: 'alias', + }, + 'pgsql.error_code': { + category: 'pgsql', + description: 'The PostgreSQL error code.', + name: 'pgsql.error_code', + type: 'long', + }, + 'pgsql.error_message': { + category: 'pgsql', + description: 'The PostgreSQL error message.', + name: 'pgsql.error_message', + }, + 'pgsql.error_severity': { + category: 'pgsql', + description: 'The PostgreSQL error severity.', + name: 'pgsql.error_severity', + }, + 'pgsql.num_fields': { + category: 'pgsql', + description: + 'If the SELECT query if successful, this field is set to the number of fields returned. ', + name: 'pgsql.num_fields', + }, + 'pgsql.num_rows': { + category: 'pgsql', + description: + 'If the SELECT query if successful, this field is set to the number of rows returned. ', + name: 'pgsql.num_rows', + }, + 'redis.return_value': { + category: 'redis', + description: 'The return value of the Redis command in a human readable format. ', + name: 'redis.return_value', + }, + 'redis.error': { + category: 'redis', + description: + 'If the Redis command has resulted in an error, this field contains the error message returned by the Redis server. ', + name: 'redis.error', + }, + 'thrift.params': { + category: 'thrift', + description: + 'The RPC method call parameters in a human readable format. If the IDL files are available, the parameters use names whenever possible. Otherwise, the IDs from the message are used. ', + name: 'thrift.params', + }, + 'thrift.service': { + category: 'thrift', + description: 'The name of the Thrift-RPC service as defined in the IDL files. ', + name: 'thrift.service', + }, + 'thrift.return_value': { + category: 'thrift', + description: + 'The value returned by the Thrift-RPC call. This is encoded in a human readable format. ', + name: 'thrift.return_value', + }, + 'thrift.exceptions': { + category: 'thrift', + description: + 'If the call resulted in exceptions, this field contains the exceptions in a human readable format. ', + name: 'thrift.exceptions', + }, + 'tls.client.x509.version': { + category: 'tls', + description: 'Version of x509 format.', + example: 3, + name: 'tls.client.x509.version', + type: 'keyword', + }, + 'tls.client.x509.version_number': { + category: 'tls', + description: 'Version of x509 format.', + example: 3, + name: 'tls.client.x509.version_number', + type: 'keyword', + }, + 'tls.client.x509.serial_number': { + category: 'tls', + description: + 'Unique serial number issued by the certificate authority. For consistency, if this value is alphanumeric, it should be formatted without colons and uppercase characters. ', + example: '55FBB9C7DEBF09809D12CCAA', + name: 'tls.client.x509.serial_number', + type: 'keyword', + }, + 'tls.client.x509.issuer.distinguished_name': { + category: 'tls', + description: 'Distinguished name (DN) of issuing certificate authority.', + example: 'C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert SHA2 High Assurance Server CA', + name: 'tls.client.x509.issuer.distinguished_name', + type: 'keyword', + }, + 'tls.client.x509.issuer.common_name': { + category: 'tls', + description: 'List of common name (CN) of issuing certificate authority.', + example: 'DigiCert SHA2 High Assurance Server CA', + name: 'tls.client.x509.issuer.common_name', + type: 'keyword', + }, + 'tls.client.x509.issuer.organizational_unit': { + category: 'tls', + description: 'List of organizational units (OU) of issuing certificate authority.', + example: 'www.digicert.com', + name: 'tls.client.x509.issuer.organizational_unit', + type: 'keyword', + }, + 'tls.client.x509.issuer.organization': { + category: 'tls', + description: 'List of organizations (O) of issuing certificate authority.', + example: 'DigiCert Inc', + name: 'tls.client.x509.issuer.organization', + type: 'keyword', + }, + 'tls.client.x509.issuer.locality': { + category: 'tls', + description: 'List of locality names (L)', + example: 'Mountain View', + name: 'tls.client.x509.issuer.locality', + type: 'keyword', + }, + 'tls.client.x509.issuer.province': { + category: 'tls', + description: 'Province or region within country.', + name: 'tls.client.x509.issuer.province', + type: 'keyword', + }, + 'tls.client.x509.issuer.state_or_province': { + category: 'tls', + description: 'List of state or province names (ST, S, or P)', + example: 'California', + name: 'tls.client.x509.issuer.state_or_province', + type: 'keyword', + }, + 'tls.client.x509.issuer.country': { + category: 'tls', + description: 'List of country (C) codes', + example: 'US', + name: 'tls.client.x509.issuer.country', + type: 'keyword', + }, + 'tls.client.x509.signature_algorithm': { + category: 'tls', + description: + 'Identifier for certificate signature algorithm. Recommend using names found in Go Lang Crypto library (See https://github.com/golang/go/blob/go1.14/src/crypto/x509/x509.go#L337-L353).', + example: 'SHA256-RSA', + name: 'tls.client.x509.signature_algorithm', + type: 'keyword', + }, + 'tls.client.x509.not_before': { + category: 'tls', + description: 'Time at which the certificate is first considered valid.', + example: '"2019-08-16T01:40:25.000Z"', + name: 'tls.client.x509.not_before', + type: 'date', + }, + 'tls.client.x509.not_after': { + category: 'tls', + description: 'Time at which the certificate is no longer considered valid.', + example: '"2020-07-16T03:15:39.000Z"', + name: 'tls.client.x509.not_after', + type: 'date', + }, + 'tls.client.x509.subject.distinguished_name': { + category: 'tls', + description: 'Distinguished name (DN) of the certificate subject entity.', + example: 'C=US, ST=California, L=San Francisco, O=Fastly, Inc., CN=r2.shared.global.fastly.net', + name: 'tls.client.x509.subject.distinguished_name', + type: 'keyword', + }, + 'tls.client.x509.subject.common_name': { + category: 'tls', + description: 'List of common names (CN) of subject.', + example: 'r2.shared.global.fastly.net', + name: 'tls.client.x509.subject.common_name', + type: 'keyword', + }, + 'tls.client.x509.subject.organizational_unit': { + category: 'tls', + description: 'List of organizational units (OU) of subject.', + name: 'tls.client.x509.subject.organizational_unit', + type: 'keyword', + }, + 'tls.client.x509.subject.organization': { + category: 'tls', + description: 'List of organizations (O) of subject.', + example: 'Fastly, Inc.', + name: 'tls.client.x509.subject.organization', + type: 'keyword', + }, + 'tls.client.x509.subject.locality': { + category: 'tls', + description: 'List of locality names (L)', + example: 'San Francisco', + name: 'tls.client.x509.subject.locality', + type: 'keyword', + }, + 'tls.client.x509.subject.province': { + category: 'tls', + description: 'Province or region within country.', + name: 'tls.client.x509.subject.province', + type: 'keyword', + }, + 'tls.client.x509.subject.state_or_province': { + category: 'tls', + description: 'List of state or province names (ST, S, or P)', + example: 'California', + name: 'tls.client.x509.subject.state_or_province', + type: 'keyword', + }, + 'tls.client.x509.subject.country': { + category: 'tls', + description: 'List of country (C) code', + example: 'US', + name: 'tls.client.x509.subject.country', + type: 'keyword', + }, + 'tls.client.x509.public_key_algorithm': { + category: 'tls', + description: 'Algorithm used to generate the public key.', + example: 'RSA', + name: 'tls.client.x509.public_key_algorithm', + type: 'keyword', + }, + 'tls.client.x509.public_key_size': { + category: 'tls', + description: 'The size of the public key space in bits.', + example: 2048, + name: 'tls.client.x509.public_key_size', + type: 'long', + }, + 'tls.client.x509.alternative_names': { + category: 'tls', + description: + 'List of subject alternative names (SAN). Name types vary by certificate authority and certificate type but commonly contain IP addresses, DNS names (and wildcards), and email addresses.', + example: '*.elastic.co', + name: 'tls.client.x509.alternative_names', + type: 'keyword', + }, + 'tls.server.x509.version': { + category: 'tls', + description: 'Version of x509 format.', + example: 3, + name: 'tls.server.x509.version', + type: 'keyword', + }, + 'tls.server.x509.version_number': { + category: 'tls', + description: 'Version of x509 format.', + example: 3, + name: 'tls.server.x509.version_number', + type: 'keyword', + }, + 'tls.server.x509.serial_number': { + category: 'tls', + description: + 'Unique serial number issued by the certificate authority. For consistency, if this value is alphanumeric, it should be formatted without colons and uppercase characters. ', + example: '55FBB9C7DEBF09809D12CCAA', + name: 'tls.server.x509.serial_number', + type: 'keyword', + }, + 'tls.server.x509.issuer.distinguished_name': { + category: 'tls', + description: 'Distinguished name (DN) of issuing certificate authority.', + example: 'C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert SHA2 High Assurance Server CA', + name: 'tls.server.x509.issuer.distinguished_name', + type: 'keyword', + }, + 'tls.server.x509.issuer.common_name': { + category: 'tls', + description: 'List of common name (CN) of issuing certificate authority.', + example: 'DigiCert SHA2 High Assurance Server CA', + name: 'tls.server.x509.issuer.common_name', + type: 'keyword', + }, + 'tls.server.x509.issuer.organizational_unit': { + category: 'tls', + description: 'List of organizational units (OU) of issuing certificate authority.', + example: 'www.digicert.com', + name: 'tls.server.x509.issuer.organizational_unit', + type: 'keyword', + }, + 'tls.server.x509.issuer.organization': { + category: 'tls', + description: 'List of organizations (O) of issuing certificate authority.', + example: 'DigiCert Inc', + name: 'tls.server.x509.issuer.organization', + type: 'keyword', + }, + 'tls.server.x509.issuer.locality': { + category: 'tls', + description: 'List of locality names (L)', + example: 'Mountain View', + name: 'tls.server.x509.issuer.locality', + type: 'keyword', + }, + 'tls.server.x509.issuer.province': { + category: 'tls', + description: 'Province or region within country.', + name: 'tls.server.x509.issuer.province', + type: 'keyword', + }, + 'tls.server.x509.issuer.state_or_province': { + category: 'tls', + description: 'List of state or province names (ST, S, or P)', + example: 'California', + name: 'tls.server.x509.issuer.state_or_province', + type: 'keyword', + }, + 'tls.server.x509.issuer.country': { + category: 'tls', + description: 'List of country (C) codes', + example: 'US', + name: 'tls.server.x509.issuer.country', + type: 'keyword', + }, + 'tls.server.x509.signature_algorithm': { + category: 'tls', + description: + 'Identifier for certificate signature algorithm. Recommend using names found in Go Lang Crypto library (See https://github.com/golang/go/blob/go1.14/src/crypto/x509/x509.go#L337-L353).', + example: 'SHA256-RSA', + name: 'tls.server.x509.signature_algorithm', + type: 'keyword', + }, + 'tls.server.x509.not_before': { + category: 'tls', + description: 'Time at which the certificate is first considered valid.', + example: '"2019-08-16T01:40:25.000Z"', + name: 'tls.server.x509.not_before', + type: 'date', + }, + 'tls.server.x509.not_after': { + category: 'tls', + description: 'Time at which the certificate is no longer considered valid.', + example: '"2020-07-16T03:15:39.000Z"', + name: 'tls.server.x509.not_after', + type: 'date', + }, + 'tls.server.x509.subject.distinguished_name': { + category: 'tls', + description: 'Distinguished name (DN) of the certificate subject entity.', + example: 'C=US, ST=California, L=San Francisco, O=Fastly, Inc., CN=r2.shared.global.fastly.net', + name: 'tls.server.x509.subject.distinguished_name', + type: 'keyword', + }, + 'tls.server.x509.subject.common_name': { + category: 'tls', + description: 'List of common names (CN) of subject.', + example: 'r2.shared.global.fastly.net', + name: 'tls.server.x509.subject.common_name', + type: 'keyword', + }, + 'tls.server.x509.subject.organizational_unit': { + category: 'tls', + description: 'List of organizational units (OU) of subject.', + name: 'tls.server.x509.subject.organizational_unit', + type: 'keyword', + }, + 'tls.server.x509.subject.organization': { + category: 'tls', + description: 'List of organizations (O) of subject.', + example: 'Fastly, Inc.', + name: 'tls.server.x509.subject.organization', + type: 'keyword', + }, + 'tls.server.x509.subject.locality': { + category: 'tls', + description: 'List of locality names (L)', + example: 'San Francisco', + name: 'tls.server.x509.subject.locality', + type: 'keyword', + }, + 'tls.server.x509.subject.province': { + category: 'tls', + description: 'Province or region within country.', + name: 'tls.server.x509.subject.province', + type: 'keyword', + }, + 'tls.server.x509.subject.state_or_province': { + category: 'tls', + description: 'List of state or province names (ST, S, or P)', + example: 'California', + name: 'tls.server.x509.subject.state_or_province', + type: 'keyword', + }, + 'tls.server.x509.subject.country': { + category: 'tls', + description: 'List of country (C) code', + example: 'US', + name: 'tls.server.x509.subject.country', + type: 'keyword', + }, + 'tls.server.x509.public_key_algorithm': { + category: 'tls', + description: 'Algorithm used to generate the public key.', + example: 'RSA', + name: 'tls.server.x509.public_key_algorithm', + type: 'keyword', + }, + 'tls.server.x509.public_key_size': { + category: 'tls', + description: 'The size of the public key space in bits.', + example: 2048, + name: 'tls.server.x509.public_key_size', + type: 'long', + }, + 'tls.server.x509.alternative_names': { + category: 'tls', + description: + 'List of subject alternative names (SAN). Name types vary by certificate authority and certificate type but commonly contain IP addresses, DNS names (and wildcards), and email addresses.', + example: '*.elastic.co', + name: 'tls.server.x509.alternative_names', + type: 'keyword', + }, + 'tls.detailed.version': { + category: 'tls', + description: 'The version of the TLS protocol used. ', + example: 'TLS 1.3', + name: 'tls.detailed.version', + type: 'keyword', + }, + 'tls.detailed.resumption_method': { + category: 'tls', + description: + 'If the session has been resumed, the underlying method used. One of "id" for TLS session ID or "ticket" for TLS ticket extension. ', + name: 'tls.detailed.resumption_method', + type: 'keyword', + }, + 'tls.detailed.client_certificate_requested': { + category: 'tls', + description: + 'Whether the server has requested the client to authenticate itself using a client certificate. ', + name: 'tls.detailed.client_certificate_requested', + type: 'boolean', + }, + 'tls.detailed.client_hello.version': { + category: 'tls', + description: + 'The version of the TLS protocol by which the client wishes to communicate during this session. ', + name: 'tls.detailed.client_hello.version', + type: 'keyword', + }, + 'tls.detailed.client_hello.session_id': { + category: 'tls', + description: + 'Unique number to identify the session for the corresponding connection with the client. ', + name: 'tls.detailed.client_hello.session_id', + type: 'keyword', + }, + 'tls.detailed.client_hello.supported_compression_methods': { + category: 'tls', + description: + 'The list of compression methods the client supports. See https://www.iana.org/assignments/comp-meth-ids/comp-meth-ids.xhtml ', + name: 'tls.detailed.client_hello.supported_compression_methods', + type: 'keyword', + }, + 'tls.detailed.client_hello.extensions.server_name_indication': { + category: 'tls', + description: 'List of hostnames', + name: 'tls.detailed.client_hello.extensions.server_name_indication', + type: 'keyword', + }, + 'tls.detailed.client_hello.extensions.application_layer_protocol_negotiation': { + category: 'tls', + description: 'List of application-layer protocols the client is willing to use. ', + name: 'tls.detailed.client_hello.extensions.application_layer_protocol_negotiation', + type: 'keyword', + }, + 'tls.detailed.client_hello.extensions.session_ticket': { + category: 'tls', + description: + 'Length of the session ticket, if provided, or an empty string to advertise support for tickets. ', + name: 'tls.detailed.client_hello.extensions.session_ticket', + type: 'keyword', + }, + 'tls.detailed.client_hello.extensions.supported_versions': { + category: 'tls', + description: 'List of TLS versions that the client is willing to use. ', + name: 'tls.detailed.client_hello.extensions.supported_versions', + type: 'keyword', + }, + 'tls.detailed.client_hello.extensions.supported_groups': { + category: 'tls', + description: 'List of Elliptic Curve Cryptography (ECC) curve groups supported by the client. ', + name: 'tls.detailed.client_hello.extensions.supported_groups', + type: 'keyword', + }, + 'tls.detailed.client_hello.extensions.signature_algorithms': { + category: 'tls', + description: 'List of signature algorithms that may be use in digital signatures. ', + name: 'tls.detailed.client_hello.extensions.signature_algorithms', + type: 'keyword', + }, + 'tls.detailed.client_hello.extensions.ec_points_formats': { + category: 'tls', + description: + 'List of Elliptic Curve (EC) point formats. Indicates the set of point formats that the client can parse. ', + name: 'tls.detailed.client_hello.extensions.ec_points_formats', + type: 'keyword', + }, + 'tls.detailed.client_hello.extensions._unparsed_': { + category: 'tls', + description: 'List of extensions that were left unparsed by Packetbeat. ', + name: 'tls.detailed.client_hello.extensions._unparsed_', + type: 'keyword', + }, + 'tls.detailed.server_hello.version': { + category: 'tls', + description: + 'The version of the TLS protocol that is used for this session. It is the highest version supported by the server not exceeding the version requested in the client hello. ', + name: 'tls.detailed.server_hello.version', + type: 'keyword', + }, + 'tls.detailed.server_hello.selected_compression_method': { + category: 'tls', + description: + 'The compression method selected by the server from the list provided in the client hello. ', + name: 'tls.detailed.server_hello.selected_compression_method', + type: 'keyword', + }, + 'tls.detailed.server_hello.session_id': { + category: 'tls', + description: + 'Unique number to identify the session for the corresponding connection with the client. ', + name: 'tls.detailed.server_hello.session_id', + type: 'keyword', + }, + 'tls.detailed.server_hello.extensions.application_layer_protocol_negotiation': { + category: 'tls', + description: 'Negotiated application layer protocol', + name: 'tls.detailed.server_hello.extensions.application_layer_protocol_negotiation', + type: 'keyword', + }, + 'tls.detailed.server_hello.extensions.session_ticket': { + category: 'tls', + description: + 'Used to announce that a session ticket will be provided by the server. Always an empty string. ', + name: 'tls.detailed.server_hello.extensions.session_ticket', + type: 'keyword', + }, + 'tls.detailed.server_hello.extensions.supported_versions': { + category: 'tls', + description: 'Negotiated TLS version to be used. ', + name: 'tls.detailed.server_hello.extensions.supported_versions', + type: 'keyword', + }, + 'tls.detailed.server_hello.extensions.ec_points_formats': { + category: 'tls', + description: + 'List of Elliptic Curve (EC) point formats. Indicates the set of point formats that the server can parse. ', + name: 'tls.detailed.server_hello.extensions.ec_points_formats', + type: 'keyword', + }, + 'tls.detailed.server_hello.extensions._unparsed_': { + category: 'tls', + description: 'List of extensions that were left unparsed by Packetbeat. ', + name: 'tls.detailed.server_hello.extensions._unparsed_', + type: 'keyword', + }, + 'tls.detailed.client_certificate.version': { + category: 'tls', + description: 'X509 format version.', + name: 'tls.detailed.client_certificate.version', + type: 'long', + }, + 'tls.detailed.client_certificate.version_number': { + category: 'tls', + description: 'Version of x509 format.', + example: 3, + name: 'tls.detailed.client_certificate.version_number', + type: 'keyword', + }, + 'tls.detailed.client_certificate.serial_number': { + category: 'tls', + description: "The certificate's serial number.", + name: 'tls.detailed.client_certificate.serial_number', + type: 'keyword', + }, + 'tls.detailed.client_certificate.not_before': { + category: 'tls', + description: 'Date before which the certificate is not valid.', + name: 'tls.detailed.client_certificate.not_before', + type: 'date', + }, + 'tls.detailed.client_certificate.not_after': { + category: 'tls', + description: 'Date after which the certificate expires.', + name: 'tls.detailed.client_certificate.not_after', + type: 'date', + }, + 'tls.detailed.client_certificate.public_key_algorithm': { + category: 'tls', + description: "The algorithm used for this certificate's public key. One of RSA, DSA or ECDSA. ", + name: 'tls.detailed.client_certificate.public_key_algorithm', + type: 'keyword', + }, + 'tls.detailed.client_certificate.public_key_size': { + category: 'tls', + description: 'Size of the public key.', + name: 'tls.detailed.client_certificate.public_key_size', + type: 'long', + }, + 'tls.detailed.client_certificate.signature_algorithm': { + category: 'tls', + description: "The algorithm used for the certificate's signature. ", + name: 'tls.detailed.client_certificate.signature_algorithm', + type: 'keyword', + }, + 'tls.detailed.client_certificate.alternative_names': { + category: 'tls', + description: 'Subject Alternative Names for this certificate.', + name: 'tls.detailed.client_certificate.alternative_names', + type: 'keyword', + }, + 'tls.detailed.client_certificate.subject.country': { + category: 'tls', + description: 'Country code.', + name: 'tls.detailed.client_certificate.subject.country', + type: 'keyword', + }, + 'tls.detailed.client_certificate.subject.organization': { + category: 'tls', + description: 'Organization name.', + name: 'tls.detailed.client_certificate.subject.organization', + type: 'keyword', + }, + 'tls.detailed.client_certificate.subject.organizational_unit': { + category: 'tls', + description: 'Unit within organization.', + name: 'tls.detailed.client_certificate.subject.organizational_unit', + type: 'keyword', + }, + 'tls.detailed.client_certificate.subject.province': { + category: 'tls', + description: 'Province or region within country.', + name: 'tls.detailed.client_certificate.subject.province', + type: 'keyword', + }, + 'tls.detailed.client_certificate.subject.common_name': { + category: 'tls', + description: 'Name or host name identified by the certificate.', + name: 'tls.detailed.client_certificate.subject.common_name', + type: 'keyword', + }, + 'tls.detailed.client_certificate.subject.locality': { + category: 'tls', + description: 'Locality.', + name: 'tls.detailed.client_certificate.subject.locality', + type: 'keyword', + }, + 'tls.detailed.client_certificate.subject.distinguished_name': { + category: 'tls', + description: 'Distinguished name (DN) of the certificate subject entity.', + example: 'C=US, ST=California, L=San Francisco, O=Fastly, Inc., CN=r2.shared.global.fastly.net', + name: 'tls.detailed.client_certificate.subject.distinguished_name', + type: 'keyword', + }, + 'tls.detailed.client_certificate.issuer.country': { + category: 'tls', + description: 'Country code.', + name: 'tls.detailed.client_certificate.issuer.country', + type: 'keyword', + }, + 'tls.detailed.client_certificate.issuer.organization': { + category: 'tls', + description: 'Organization name.', + name: 'tls.detailed.client_certificate.issuer.organization', + type: 'keyword', + }, + 'tls.detailed.client_certificate.issuer.organizational_unit': { + category: 'tls', + description: 'Unit within organization.', + name: 'tls.detailed.client_certificate.issuer.organizational_unit', + type: 'keyword', + }, + 'tls.detailed.client_certificate.issuer.province': { + category: 'tls', + description: 'Province or region within country.', + name: 'tls.detailed.client_certificate.issuer.province', + type: 'keyword', + }, + 'tls.detailed.client_certificate.issuer.common_name': { + category: 'tls', + description: 'Name or host name identified by the certificate.', + name: 'tls.detailed.client_certificate.issuer.common_name', + type: 'keyword', + }, + 'tls.detailed.client_certificate.issuer.locality': { + category: 'tls', + description: 'Locality.', + name: 'tls.detailed.client_certificate.issuer.locality', + type: 'keyword', + }, + 'tls.detailed.client_certificate.issuer.distinguished_name': { + category: 'tls', + description: 'Distinguished name (DN) of the certificate issuer entity.', + example: 'C=US, ST=California, L=San Francisco, O=Fastly, Inc., CN=r2.shared.global.fastly.net', + name: 'tls.detailed.client_certificate.issuer.distinguished_name', + type: 'keyword', + }, + 'tls.detailed.server_certificate.version': { + category: 'tls', + description: 'X509 format version.', + name: 'tls.detailed.server_certificate.version', + type: 'long', + }, + 'tls.detailed.server_certificate.version_number': { + category: 'tls', + description: 'Version of x509 format.', + example: 3, + name: 'tls.detailed.server_certificate.version_number', + type: 'keyword', + }, + 'tls.detailed.server_certificate.serial_number': { + category: 'tls', + description: "The certificate's serial number.", + name: 'tls.detailed.server_certificate.serial_number', + type: 'keyword', + }, + 'tls.detailed.server_certificate.not_before': { + category: 'tls', + description: 'Date before which the certificate is not valid.', + name: 'tls.detailed.server_certificate.not_before', + type: 'date', + }, + 'tls.detailed.server_certificate.not_after': { + category: 'tls', + description: 'Date after which the certificate expires.', + name: 'tls.detailed.server_certificate.not_after', + type: 'date', + }, + 'tls.detailed.server_certificate.public_key_algorithm': { + category: 'tls', + description: "The algorithm used for this certificate's public key. One of RSA, DSA or ECDSA. ", + name: 'tls.detailed.server_certificate.public_key_algorithm', + type: 'keyword', + }, + 'tls.detailed.server_certificate.public_key_size': { + category: 'tls', + description: 'Size of the public key.', + name: 'tls.detailed.server_certificate.public_key_size', + type: 'long', + }, + 'tls.detailed.server_certificate.signature_algorithm': { + category: 'tls', + description: "The algorithm used for the certificate's signature. ", + name: 'tls.detailed.server_certificate.signature_algorithm', + type: 'keyword', + }, + 'tls.detailed.server_certificate.alternative_names': { + category: 'tls', + description: 'Subject Alternative Names for this certificate.', + name: 'tls.detailed.server_certificate.alternative_names', + type: 'keyword', + }, + 'tls.detailed.server_certificate.subject.country': { + category: 'tls', + description: 'Country code.', + name: 'tls.detailed.server_certificate.subject.country', + type: 'keyword', + }, + 'tls.detailed.server_certificate.subject.organization': { + category: 'tls', + description: 'Organization name.', + name: 'tls.detailed.server_certificate.subject.organization', + type: 'keyword', + }, + 'tls.detailed.server_certificate.subject.organizational_unit': { + category: 'tls', + description: 'Unit within organization.', + name: 'tls.detailed.server_certificate.subject.organizational_unit', + type: 'keyword', + }, + 'tls.detailed.server_certificate.subject.province': { + category: 'tls', + description: 'Province or region within country.', + name: 'tls.detailed.server_certificate.subject.province', + type: 'keyword', + }, + 'tls.detailed.server_certificate.subject.state_or_province': { + category: 'tls', + description: 'Province or region within country.', + name: 'tls.detailed.server_certificate.subject.state_or_province', + type: 'keyword', + }, + 'tls.detailed.server_certificate.subject.common_name': { + category: 'tls', + description: 'Name or host name identified by the certificate.', + name: 'tls.detailed.server_certificate.subject.common_name', + type: 'keyword', + }, + 'tls.detailed.server_certificate.subject.locality': { + category: 'tls', + description: 'Locality.', + name: 'tls.detailed.server_certificate.subject.locality', + type: 'keyword', + }, + 'tls.detailed.server_certificate.subject.distinguished_name': { + category: 'tls', + description: 'Distinguished name (DN) of the certificate subject entity.', + example: 'C=US, ST=California, L=San Francisco, O=Fastly, Inc., CN=r2.shared.global.fastly.net', + name: 'tls.detailed.server_certificate.subject.distinguished_name', + type: 'keyword', + }, + 'tls.detailed.server_certificate.issuer.country': { + category: 'tls', + description: 'Country code.', + name: 'tls.detailed.server_certificate.issuer.country', + type: 'keyword', + }, + 'tls.detailed.server_certificate.issuer.organization': { + category: 'tls', + description: 'Organization name.', + name: 'tls.detailed.server_certificate.issuer.organization', + type: 'keyword', + }, + 'tls.detailed.server_certificate.issuer.organizational_unit': { + category: 'tls', + description: 'Unit within organization.', + name: 'tls.detailed.server_certificate.issuer.organizational_unit', + type: 'keyword', + }, + 'tls.detailed.server_certificate.issuer.province': { + category: 'tls', + description: 'Province or region within country.', + name: 'tls.detailed.server_certificate.issuer.province', + type: 'keyword', + }, + 'tls.detailed.server_certificate.issuer.state_or_province': { + category: 'tls', + description: 'Province or region within country.', + name: 'tls.detailed.server_certificate.issuer.state_or_province', + type: 'keyword', + }, + 'tls.detailed.server_certificate.issuer.common_name': { + category: 'tls', + description: 'Name or host name identified by the certificate.', + name: 'tls.detailed.server_certificate.issuer.common_name', + type: 'keyword', + }, + 'tls.detailed.server_certificate.issuer.locality': { + category: 'tls', + description: 'Locality.', + name: 'tls.detailed.server_certificate.issuer.locality', + type: 'keyword', + }, + 'tls.detailed.server_certificate.issuer.distinguished_name': { + category: 'tls', + description: 'Distinguished name (DN) of the certificate issuer entity.', + example: 'C=US, ST=California, L=San Francisco, O=Fastly, Inc., CN=r2.shared.global.fastly.net', + name: 'tls.detailed.server_certificate.issuer.distinguished_name', + type: 'keyword', + }, + 'tls.detailed.server_certificate_chain': { + category: 'tls', + description: 'Chain of trust for the server certificate.', + name: 'tls.detailed.server_certificate_chain', + type: 'array', + }, + 'tls.detailed.client_certificate_chain': { + category: 'tls', + description: 'Chain of trust for the client certificate.', + name: 'tls.detailed.client_certificate_chain', + type: 'array', + }, + 'tls.detailed.alert_types': { + category: 'tls', + description: 'An array containing the TLS alert type for every alert received. ', + name: 'tls.detailed.alert_types', + type: 'keyword', + }, + 'tls.handshake_completed': { + category: 'tls', + name: 'tls.handshake_completed', + type: 'alias', + }, + 'tls.client_hello.supported_ciphers': { + category: 'tls', + name: 'tls.client_hello.supported_ciphers', + type: 'alias', + }, + 'tls.server_hello.selected_cipher': { + category: 'tls', + name: 'tls.server_hello.selected_cipher', + type: 'alias', + }, + 'tls.fingerprints.ja3': { + category: 'tls', + name: 'tls.fingerprints.ja3', + type: 'alias', + }, + 'tls.resumption_method': { + category: 'tls', + name: 'tls.resumption_method', + type: 'alias', + }, + 'tls.client_certificate_requested': { + category: 'tls', + name: 'tls.client_certificate_requested', + type: 'alias', + }, + 'tls.client_hello.version': { + category: 'tls', + name: 'tls.client_hello.version', + type: 'alias', + }, + 'tls.client_hello.session_id': { + category: 'tls', + name: 'tls.client_hello.session_id', + type: 'alias', + }, + 'tls.client_hello.supported_compression_methods': { + category: 'tls', + name: 'tls.client_hello.supported_compression_methods', + type: 'alias', + }, + 'tls.client_hello.extensions.server_name_indication': { + category: 'tls', + name: 'tls.client_hello.extensions.server_name_indication', + type: 'alias', + }, + 'tls.client_hello.extensions.application_layer_protocol_negotiation': { + category: 'tls', + name: 'tls.client_hello.extensions.application_layer_protocol_negotiation', + type: 'alias', + }, + 'tls.client_hello.extensions.session_ticket': { + category: 'tls', + name: 'tls.client_hello.extensions.session_ticket', + type: 'alias', + }, + 'tls.client_hello.extensions.supported_versions': { + category: 'tls', + name: 'tls.client_hello.extensions.supported_versions', + type: 'alias', + }, + 'tls.client_hello.extensions.supported_groups': { + category: 'tls', + name: 'tls.client_hello.extensions.supported_groups', + type: 'alias', + }, + 'tls.client_hello.extensions.signature_algorithms': { + category: 'tls', + name: 'tls.client_hello.extensions.signature_algorithms', + type: 'alias', + }, + 'tls.client_hello.extensions.ec_points_formats': { + category: 'tls', + name: 'tls.client_hello.extensions.ec_points_formats', + type: 'alias', + }, + 'tls.client_hello.extensions._unparsed_': { + category: 'tls', + name: 'tls.client_hello.extensions._unparsed_', + type: 'alias', + }, + 'tls.server_hello.version': { + category: 'tls', + name: 'tls.server_hello.version', + type: 'alias', + }, + 'tls.server_hello.selected_compression_method': { + category: 'tls', + name: 'tls.server_hello.selected_compression_method', + type: 'alias', + }, + 'tls.server_hello.session_id': { + category: 'tls', + name: 'tls.server_hello.session_id', + type: 'alias', + }, + 'tls.server_hello.extensions.application_layer_protocol_negotiation': { + category: 'tls', + name: 'tls.server_hello.extensions.application_layer_protocol_negotiation', + type: 'alias', + }, + 'tls.server_hello.extensions.session_ticket': { + category: 'tls', + name: 'tls.server_hello.extensions.session_ticket', + type: 'alias', + }, + 'tls.server_hello.extensions.supported_versions': { + category: 'tls', + name: 'tls.server_hello.extensions.supported_versions', + type: 'alias', + }, + 'tls.server_hello.extensions.ec_points_formats': { + category: 'tls', + name: 'tls.server_hello.extensions.ec_points_formats', + type: 'alias', + }, + 'tls.server_hello.extensions._unparsed_': { + category: 'tls', + name: 'tls.server_hello.extensions._unparsed_', + type: 'alias', + }, + 'tls.client_certificate.version': { + category: 'tls', + name: 'tls.client_certificate.version', + type: 'alias', + }, + 'tls.client_certificate.serial_number': { + category: 'tls', + name: 'tls.client_certificate.serial_number', + type: 'alias', + }, + 'tls.client_certificate.not_before': { + category: 'tls', + name: 'tls.client_certificate.not_before', + type: 'alias', + }, + 'tls.client_certificate.not_after': { + category: 'tls', + name: 'tls.client_certificate.not_after', + type: 'alias', + }, + 'tls.client_certificate.public_key_algorithm': { + category: 'tls', + name: 'tls.client_certificate.public_key_algorithm', + type: 'alias', + }, + 'tls.client_certificate.public_key_size': { + category: 'tls', + name: 'tls.client_certificate.public_key_size', + type: 'alias', + }, + 'tls.client_certificate.signature_algorithm': { + category: 'tls', + name: 'tls.client_certificate.signature_algorithm', + type: 'alias', + }, + 'tls.client_certificate.alternative_names': { + category: 'tls', + name: 'tls.client_certificate.alternative_names', + type: 'alias', + }, + 'tls.client_certificate.subject.country': { + category: 'tls', + name: 'tls.client_certificate.subject.country', + type: 'alias', + }, + 'tls.client_certificate.subject.organization': { + category: 'tls', + name: 'tls.client_certificate.subject.organization', + type: 'alias', + }, + 'tls.client_certificate.subject.organizational_unit': { + category: 'tls', + name: 'tls.client_certificate.subject.organizational_unit', + type: 'alias', + }, + 'tls.client_certificate.subject.province': { + category: 'tls', + name: 'tls.client_certificate.subject.province', + type: 'alias', + }, + 'tls.client_certificate.subject.common_name': { + category: 'tls', + name: 'tls.client_certificate.subject.common_name', + type: 'alias', + }, + 'tls.client_certificate.subject.locality': { + category: 'tls', + name: 'tls.client_certificate.subject.locality', + type: 'alias', + }, + 'tls.client_certificate.issuer.country': { + category: 'tls', + name: 'tls.client_certificate.issuer.country', + type: 'alias', + }, + 'tls.client_certificate.issuer.organization': { + category: 'tls', + name: 'tls.client_certificate.issuer.organization', + type: 'alias', + }, + 'tls.client_certificate.issuer.organizational_unit': { + category: 'tls', + name: 'tls.client_certificate.issuer.organizational_unit', + type: 'alias', + }, + 'tls.client_certificate.issuer.province': { + category: 'tls', + name: 'tls.client_certificate.issuer.province', + type: 'alias', + }, + 'tls.client_certificate.issuer.common_name': { + category: 'tls', + name: 'tls.client_certificate.issuer.common_name', + type: 'alias', + }, + 'tls.client_certificate.issuer.locality': { + category: 'tls', + name: 'tls.client_certificate.issuer.locality', + type: 'alias', + }, + 'tls.server_certificate.version': { + category: 'tls', + name: 'tls.server_certificate.version', + type: 'alias', + }, + 'tls.server_certificate.serial_number': { + category: 'tls', + name: 'tls.server_certificate.serial_number', + type: 'alias', + }, + 'tls.server_certificate.not_before': { + category: 'tls', + name: 'tls.server_certificate.not_before', + type: 'alias', + }, + 'tls.server_certificate.not_after': { + category: 'tls', + name: 'tls.server_certificate.not_after', + type: 'alias', + }, + 'tls.server_certificate.public_key_algorithm': { + category: 'tls', + name: 'tls.server_certificate.public_key_algorithm', + type: 'alias', + }, + 'tls.server_certificate.public_key_size': { + category: 'tls', + name: 'tls.server_certificate.public_key_size', + type: 'alias', + }, + 'tls.server_certificate.signature_algorithm': { + category: 'tls', + name: 'tls.server_certificate.signature_algorithm', + type: 'alias', + }, + 'tls.server_certificate.alternative_names': { + category: 'tls', + name: 'tls.server_certificate.alternative_names', + type: 'alias', + }, + 'tls.server_certificate.subject.country': { + category: 'tls', + name: 'tls.server_certificate.subject.country', + type: 'alias', + }, + 'tls.server_certificate.subject.organization': { + category: 'tls', + name: 'tls.server_certificate.subject.organization', + type: 'alias', + }, + 'tls.server_certificate.subject.organizational_unit': { + category: 'tls', + name: 'tls.server_certificate.subject.organizational_unit', + type: 'alias', + }, + 'tls.server_certificate.subject.province': { + category: 'tls', + name: 'tls.server_certificate.subject.province', + type: 'alias', + }, + 'tls.server_certificate.subject.common_name': { + category: 'tls', + name: 'tls.server_certificate.subject.common_name', + type: 'alias', + }, + 'tls.server_certificate.subject.locality': { + category: 'tls', + name: 'tls.server_certificate.subject.locality', + type: 'alias', + }, + 'tls.server_certificate.issuer.country': { + category: 'tls', + name: 'tls.server_certificate.issuer.country', + type: 'alias', + }, + 'tls.server_certificate.issuer.organization': { + category: 'tls', + name: 'tls.server_certificate.issuer.organization', + type: 'alias', + }, + 'tls.server_certificate.issuer.organizational_unit': { + category: 'tls', + name: 'tls.server_certificate.issuer.organizational_unit', + type: 'alias', + }, + 'tls.server_certificate.issuer.province': { + category: 'tls', + name: 'tls.server_certificate.issuer.province', + type: 'alias', + }, + 'tls.server_certificate.issuer.common_name': { + category: 'tls', + name: 'tls.server_certificate.issuer.common_name', + type: 'alias', + }, + 'tls.server_certificate.issuer.locality': { + category: 'tls', + name: 'tls.server_certificate.issuer.locality', + type: 'alias', + }, + 'tls.alert_types': { + category: 'tls', + name: 'tls.alert_types', + type: 'alias', + }, + 'winlog.api': { + category: 'winlog', + description: + 'The event log API type used to read the record. The possible values are "wineventlog" for the Windows Event Log API or "eventlogging" for the Event Logging API. The Event Logging API was designed for Windows Server 2003 or Windows 2000 operating systems. In Windows Vista, the event logging infrastructure was redesigned. On Windows Vista or later operating systems, the Windows Event Log API is used. Winlogbeat automatically detects which API to use for reading event logs. ', + name: 'winlog.api', + }, + 'winlog.activity_id': { + category: 'winlog', + description: + 'A globally unique identifier that identifies the current activity. The events that are published with this identifier are part of the same activity. ', + name: 'winlog.activity_id', + type: 'keyword', + }, + 'winlog.computer_name': { + category: 'winlog', + description: + 'The name of the computer that generated the record. When using Windows event forwarding, this name can differ from `agent.hostname`. ', + name: 'winlog.computer_name', + type: 'keyword', + }, + 'winlog.event_data': { + category: 'winlog', + description: + 'The event-specific data. This field is mutually exclusive with `user_data`. If you are capturing event data on versions prior to Windows Vista, the parameters in `event_data` are named `param1`, `param2`, and so on, because event log parameters are unnamed in earlier versions of Windows. ', + name: 'winlog.event_data', + type: 'object', + }, + 'winlog.event_data.AuthenticationPackageName': { + category: 'winlog', + name: 'winlog.event_data.AuthenticationPackageName', + type: 'keyword', + }, + 'winlog.event_data.Binary': { + category: 'winlog', + name: 'winlog.event_data.Binary', + type: 'keyword', + }, + 'winlog.event_data.BitlockerUserInputTime': { + category: 'winlog', + name: 'winlog.event_data.BitlockerUserInputTime', + type: 'keyword', + }, + 'winlog.event_data.BootMode': { + category: 'winlog', + name: 'winlog.event_data.BootMode', + type: 'keyword', + }, + 'winlog.event_data.BootType': { + category: 'winlog', + name: 'winlog.event_data.BootType', + type: 'keyword', + }, + 'winlog.event_data.BuildVersion': { + category: 'winlog', + name: 'winlog.event_data.BuildVersion', + type: 'keyword', + }, + 'winlog.event_data.Company': { + category: 'winlog', + name: 'winlog.event_data.Company', + type: 'keyword', + }, + 'winlog.event_data.CorruptionActionState': { + category: 'winlog', + name: 'winlog.event_data.CorruptionActionState', + type: 'keyword', + }, + 'winlog.event_data.CreationUtcTime': { + category: 'winlog', + name: 'winlog.event_data.CreationUtcTime', + type: 'keyword', + }, + 'winlog.event_data.Description': { + category: 'winlog', + name: 'winlog.event_data.Description', + type: 'keyword', + }, + 'winlog.event_data.Detail': { + category: 'winlog', + name: 'winlog.event_data.Detail', + type: 'keyword', + }, + 'winlog.event_data.DeviceName': { + category: 'winlog', + name: 'winlog.event_data.DeviceName', + type: 'keyword', + }, + 'winlog.event_data.DeviceNameLength': { + category: 'winlog', + name: 'winlog.event_data.DeviceNameLength', + type: 'keyword', + }, + 'winlog.event_data.DeviceTime': { + category: 'winlog', + name: 'winlog.event_data.DeviceTime', + type: 'keyword', + }, + 'winlog.event_data.DeviceVersionMajor': { + category: 'winlog', + name: 'winlog.event_data.DeviceVersionMajor', + type: 'keyword', + }, + 'winlog.event_data.DeviceVersionMinor': { + category: 'winlog', + name: 'winlog.event_data.DeviceVersionMinor', + type: 'keyword', + }, + 'winlog.event_data.DriveName': { + category: 'winlog', + name: 'winlog.event_data.DriveName', + type: 'keyword', + }, + 'winlog.event_data.DriverName': { + category: 'winlog', + name: 'winlog.event_data.DriverName', + type: 'keyword', + }, + 'winlog.event_data.DriverNameLength': { + category: 'winlog', + name: 'winlog.event_data.DriverNameLength', + type: 'keyword', + }, + 'winlog.event_data.DwordVal': { + category: 'winlog', + name: 'winlog.event_data.DwordVal', + type: 'keyword', + }, + 'winlog.event_data.EntryCount': { + category: 'winlog', + name: 'winlog.event_data.EntryCount', + type: 'keyword', + }, + 'winlog.event_data.ExtraInfo': { + category: 'winlog', + name: 'winlog.event_data.ExtraInfo', + type: 'keyword', + }, + 'winlog.event_data.FailureName': { + category: 'winlog', + name: 'winlog.event_data.FailureName', + type: 'keyword', + }, + 'winlog.event_data.FailureNameLength': { + category: 'winlog', + name: 'winlog.event_data.FailureNameLength', + type: 'keyword', + }, + 'winlog.event_data.FileVersion': { + category: 'winlog', + name: 'winlog.event_data.FileVersion', + type: 'keyword', + }, + 'winlog.event_data.FinalStatus': { + category: 'winlog', + name: 'winlog.event_data.FinalStatus', + type: 'keyword', + }, + 'winlog.event_data.Group': { + category: 'winlog', + name: 'winlog.event_data.Group', + type: 'keyword', + }, + 'winlog.event_data.IdleImplementation': { + category: 'winlog', + name: 'winlog.event_data.IdleImplementation', + type: 'keyword', + }, + 'winlog.event_data.IdleStateCount': { + category: 'winlog', + name: 'winlog.event_data.IdleStateCount', + type: 'keyword', + }, + 'winlog.event_data.ImpersonationLevel': { + category: 'winlog', + name: 'winlog.event_data.ImpersonationLevel', + type: 'keyword', + }, + 'winlog.event_data.IntegrityLevel': { + category: 'winlog', + name: 'winlog.event_data.IntegrityLevel', + type: 'keyword', + }, + 'winlog.event_data.IpAddress': { + category: 'winlog', + name: 'winlog.event_data.IpAddress', + type: 'keyword', + }, + 'winlog.event_data.IpPort': { + category: 'winlog', + name: 'winlog.event_data.IpPort', + type: 'keyword', + }, + 'winlog.event_data.KeyLength': { + category: 'winlog', + name: 'winlog.event_data.KeyLength', + type: 'keyword', + }, + 'winlog.event_data.LastBootGood': { + category: 'winlog', + name: 'winlog.event_data.LastBootGood', + type: 'keyword', + }, + 'winlog.event_data.LastShutdownGood': { + category: 'winlog', + name: 'winlog.event_data.LastShutdownGood', + type: 'keyword', + }, + 'winlog.event_data.LmPackageName': { + category: 'winlog', + name: 'winlog.event_data.LmPackageName', + type: 'keyword', + }, + 'winlog.event_data.LogonGuid': { + category: 'winlog', + name: 'winlog.event_data.LogonGuid', + type: 'keyword', + }, + 'winlog.event_data.LogonId': { + category: 'winlog', + name: 'winlog.event_data.LogonId', + type: 'keyword', + }, + 'winlog.event_data.LogonProcessName': { + category: 'winlog', + name: 'winlog.event_data.LogonProcessName', + type: 'keyword', + }, + 'winlog.event_data.LogonType': { + category: 'winlog', + name: 'winlog.event_data.LogonType', + type: 'keyword', + }, + 'winlog.event_data.MajorVersion': { + category: 'winlog', + name: 'winlog.event_data.MajorVersion', + type: 'keyword', + }, + 'winlog.event_data.MaximumPerformancePercent': { + category: 'winlog', + name: 'winlog.event_data.MaximumPerformancePercent', + type: 'keyword', + }, + 'winlog.event_data.MemberName': { + category: 'winlog', + name: 'winlog.event_data.MemberName', + type: 'keyword', + }, + 'winlog.event_data.MemberSid': { + category: 'winlog', + name: 'winlog.event_data.MemberSid', + type: 'keyword', + }, + 'winlog.event_data.MinimumPerformancePercent': { + category: 'winlog', + name: 'winlog.event_data.MinimumPerformancePercent', + type: 'keyword', + }, + 'winlog.event_data.MinimumThrottlePercent': { + category: 'winlog', + name: 'winlog.event_data.MinimumThrottlePercent', + type: 'keyword', + }, + 'winlog.event_data.MinorVersion': { + category: 'winlog', + name: 'winlog.event_data.MinorVersion', + type: 'keyword', + }, + 'winlog.event_data.NewProcessId': { + category: 'winlog', + name: 'winlog.event_data.NewProcessId', + type: 'keyword', + }, + 'winlog.event_data.NewProcessName': { + category: 'winlog', + name: 'winlog.event_data.NewProcessName', + type: 'keyword', + }, + 'winlog.event_data.NewSchemeGuid': { + category: 'winlog', + name: 'winlog.event_data.NewSchemeGuid', + type: 'keyword', + }, + 'winlog.event_data.NewTime': { + category: 'winlog', + name: 'winlog.event_data.NewTime', + type: 'keyword', + }, + 'winlog.event_data.NominalFrequency': { + category: 'winlog', + name: 'winlog.event_data.NominalFrequency', + type: 'keyword', + }, + 'winlog.event_data.Number': { + category: 'winlog', + name: 'winlog.event_data.Number', + type: 'keyword', + }, + 'winlog.event_data.OldSchemeGuid': { + category: 'winlog', + name: 'winlog.event_data.OldSchemeGuid', + type: 'keyword', + }, + 'winlog.event_data.OldTime': { + category: 'winlog', + name: 'winlog.event_data.OldTime', + type: 'keyword', + }, + 'winlog.event_data.OriginalFileName': { + category: 'winlog', + name: 'winlog.event_data.OriginalFileName', + type: 'keyword', + }, + 'winlog.event_data.Path': { + category: 'winlog', + name: 'winlog.event_data.Path', + type: 'keyword', + }, + 'winlog.event_data.PerformanceImplementation': { + category: 'winlog', + name: 'winlog.event_data.PerformanceImplementation', + type: 'keyword', + }, + 'winlog.event_data.PreviousCreationUtcTime': { + category: 'winlog', + name: 'winlog.event_data.PreviousCreationUtcTime', + type: 'keyword', + }, + 'winlog.event_data.PreviousTime': { + category: 'winlog', + name: 'winlog.event_data.PreviousTime', + type: 'keyword', + }, + 'winlog.event_data.PrivilegeList': { + category: 'winlog', + name: 'winlog.event_data.PrivilegeList', + type: 'keyword', + }, + 'winlog.event_data.ProcessId': { + category: 'winlog', + name: 'winlog.event_data.ProcessId', + type: 'keyword', + }, + 'winlog.event_data.ProcessName': { + category: 'winlog', + name: 'winlog.event_data.ProcessName', + type: 'keyword', + }, + 'winlog.event_data.ProcessPath': { + category: 'winlog', + name: 'winlog.event_data.ProcessPath', + type: 'keyword', + }, + 'winlog.event_data.ProcessPid': { + category: 'winlog', + name: 'winlog.event_data.ProcessPid', + type: 'keyword', + }, + 'winlog.event_data.Product': { + category: 'winlog', + name: 'winlog.event_data.Product', + type: 'keyword', + }, + 'winlog.event_data.PuaCount': { + category: 'winlog', + name: 'winlog.event_data.PuaCount', + type: 'keyword', + }, + 'winlog.event_data.PuaPolicyId': { + category: 'winlog', + name: 'winlog.event_data.PuaPolicyId', + type: 'keyword', + }, + 'winlog.event_data.QfeVersion': { + category: 'winlog', + name: 'winlog.event_data.QfeVersion', + type: 'keyword', + }, + 'winlog.event_data.Reason': { + category: 'winlog', + name: 'winlog.event_data.Reason', + type: 'keyword', + }, + 'winlog.event_data.SchemaVersion': { + category: 'winlog', + name: 'winlog.event_data.SchemaVersion', + type: 'keyword', + }, + 'winlog.event_data.ScriptBlockText': { + category: 'winlog', + name: 'winlog.event_data.ScriptBlockText', + type: 'keyword', + }, + 'winlog.event_data.ServiceName': { + category: 'winlog', + name: 'winlog.event_data.ServiceName', + type: 'keyword', + }, + 'winlog.event_data.ServiceVersion': { + category: 'winlog', + name: 'winlog.event_data.ServiceVersion', + type: 'keyword', + }, + 'winlog.event_data.ShutdownActionType': { + category: 'winlog', + name: 'winlog.event_data.ShutdownActionType', + type: 'keyword', + }, + 'winlog.event_data.ShutdownEventCode': { + category: 'winlog', + name: 'winlog.event_data.ShutdownEventCode', + type: 'keyword', + }, + 'winlog.event_data.ShutdownReason': { + category: 'winlog', + name: 'winlog.event_data.ShutdownReason', + type: 'keyword', + }, + 'winlog.event_data.Signature': { + category: 'winlog', + name: 'winlog.event_data.Signature', + type: 'keyword', + }, + 'winlog.event_data.SignatureStatus': { + category: 'winlog', + name: 'winlog.event_data.SignatureStatus', + type: 'keyword', + }, + 'winlog.event_data.Signed': { + category: 'winlog', + name: 'winlog.event_data.Signed', + type: 'keyword', + }, + 'winlog.event_data.StartTime': { + category: 'winlog', + name: 'winlog.event_data.StartTime', + type: 'keyword', + }, + 'winlog.event_data.State': { + category: 'winlog', + name: 'winlog.event_data.State', + type: 'keyword', + }, + 'winlog.event_data.Status': { + category: 'winlog', + name: 'winlog.event_data.Status', + type: 'keyword', + }, + 'winlog.event_data.StopTime': { + category: 'winlog', + name: 'winlog.event_data.StopTime', + type: 'keyword', + }, + 'winlog.event_data.SubjectDomainName': { + category: 'winlog', + name: 'winlog.event_data.SubjectDomainName', + type: 'keyword', + }, + 'winlog.event_data.SubjectLogonId': { + category: 'winlog', + name: 'winlog.event_data.SubjectLogonId', + type: 'keyword', + }, + 'winlog.event_data.SubjectUserName': { + category: 'winlog', + name: 'winlog.event_data.SubjectUserName', + type: 'keyword', + }, + 'winlog.event_data.SubjectUserSid': { + category: 'winlog', + name: 'winlog.event_data.SubjectUserSid', + type: 'keyword', + }, + 'winlog.event_data.TSId': { + category: 'winlog', + name: 'winlog.event_data.TSId', + type: 'keyword', + }, + 'winlog.event_data.TargetDomainName': { + category: 'winlog', + name: 'winlog.event_data.TargetDomainName', + type: 'keyword', + }, + 'winlog.event_data.TargetInfo': { + category: 'winlog', + name: 'winlog.event_data.TargetInfo', + type: 'keyword', + }, + 'winlog.event_data.TargetLogonGuid': { + category: 'winlog', + name: 'winlog.event_data.TargetLogonGuid', + type: 'keyword', + }, + 'winlog.event_data.TargetLogonId': { + category: 'winlog', + name: 'winlog.event_data.TargetLogonId', + type: 'keyword', + }, + 'winlog.event_data.TargetServerName': { + category: 'winlog', + name: 'winlog.event_data.TargetServerName', + type: 'keyword', + }, + 'winlog.event_data.TargetUserName': { + category: 'winlog', + name: 'winlog.event_data.TargetUserName', + type: 'keyword', + }, + 'winlog.event_data.TargetUserSid': { + category: 'winlog', + name: 'winlog.event_data.TargetUserSid', + type: 'keyword', + }, + 'winlog.event_data.TerminalSessionId': { + category: 'winlog', + name: 'winlog.event_data.TerminalSessionId', + type: 'keyword', + }, + 'winlog.event_data.TokenElevationType': { + category: 'winlog', + name: 'winlog.event_data.TokenElevationType', + type: 'keyword', + }, + 'winlog.event_data.TransmittedServices': { + category: 'winlog', + name: 'winlog.event_data.TransmittedServices', + type: 'keyword', + }, + 'winlog.event_data.UserSid': { + category: 'winlog', + name: 'winlog.event_data.UserSid', + type: 'keyword', + }, + 'winlog.event_data.Version': { + category: 'winlog', + name: 'winlog.event_data.Version', + type: 'keyword', + }, + 'winlog.event_data.Workstation': { + category: 'winlog', + name: 'winlog.event_data.Workstation', + type: 'keyword', + }, + 'winlog.event_data.param1': { + category: 'winlog', + name: 'winlog.event_data.param1', + type: 'keyword', + }, + 'winlog.event_data.param2': { + category: 'winlog', + name: 'winlog.event_data.param2', + type: 'keyword', + }, + 'winlog.event_data.param3': { + category: 'winlog', + name: 'winlog.event_data.param3', + type: 'keyword', + }, + 'winlog.event_data.param4': { + category: 'winlog', + name: 'winlog.event_data.param4', + type: 'keyword', + }, + 'winlog.event_data.param5': { + category: 'winlog', + name: 'winlog.event_data.param5', + type: 'keyword', + }, + 'winlog.event_data.param6': { + category: 'winlog', + name: 'winlog.event_data.param6', + type: 'keyword', + }, + 'winlog.event_data.param7': { + category: 'winlog', + name: 'winlog.event_data.param7', + type: 'keyword', + }, + 'winlog.event_data.param8': { + category: 'winlog', + name: 'winlog.event_data.param8', + type: 'keyword', + }, + 'winlog.event_id': { + category: 'winlog', + description: 'The event identifier. The value is specific to the source of the event. ', + name: 'winlog.event_id', + type: 'keyword', + }, + 'winlog.keywords': { + category: 'winlog', + description: 'The keywords are used to classify an event. ', + name: 'winlog.keywords', + type: 'keyword', + }, + 'winlog.channel': { + category: 'winlog', + description: + 'The name of the channel from which this record was read. This value is one of the names from the `event_logs` collection in the configuration. ', + name: 'winlog.channel', + type: 'keyword', + }, + 'winlog.record_id': { + category: 'winlog', + description: + 'The record ID of the event log record. The first record written to an event log is record number 1, and other records are numbered sequentially. If the record number reaches the maximum value (2^32^ for the Event Logging API and 2^64^ for the Windows Event Log API), the next record number will be 0. ', + name: 'winlog.record_id', + type: 'keyword', + }, + 'winlog.related_activity_id': { + category: 'winlog', + description: + 'A globally unique identifier that identifies the activity to which control was transferred to. The related events would then have this identifier as their `activity_id` identifier. ', + name: 'winlog.related_activity_id', + type: 'keyword', + }, + 'winlog.opcode': { + category: 'winlog', + description: + 'The opcode defined in the event. Task and opcode are typically used to identify the location in the application from where the event was logged. ', + name: 'winlog.opcode', + type: 'keyword', + }, + 'winlog.provider_guid': { + category: 'winlog', + description: + 'A globally unique identifier that identifies the provider that logged the event. ', + name: 'winlog.provider_guid', + type: 'keyword', + }, + 'winlog.process.pid': { + category: 'winlog', + description: 'The process_id of the Client Server Runtime Process. ', + name: 'winlog.process.pid', + type: 'long', + }, + 'winlog.provider_name': { + category: 'winlog', + description: + 'The source of the event log record (the application or service that logged the record). ', + name: 'winlog.provider_name', + type: 'keyword', + }, + 'winlog.task': { + category: 'winlog', + description: + 'The task defined in the event. Task and opcode are typically used to identify the location in the application from where the event was logged. The category used by the Event Logging API (on pre Windows Vista operating systems) is written to this field. ', + name: 'winlog.task', + type: 'keyword', + }, + 'winlog.process.thread.id': { + category: 'winlog', + name: 'winlog.process.thread.id', + type: 'long', + }, + 'winlog.user_data': { + category: 'winlog', + description: 'The event specific data. This field is mutually exclusive with `event_data`. ', + name: 'winlog.user_data', + type: 'object', + }, + 'winlog.user.identifier': { + category: 'winlog', + description: + 'The Windows security identifier (SID) of the account associated with this event. If Winlogbeat cannot resolve the SID to a name, then the `user.name`, `user.domain`, and `user.type` fields will be omitted from the event. If you discover Winlogbeat not resolving SIDs, review the log for clues as to what the problem may be. ', + example: 'S-1-5-21-3541430928-2051711210-1391384369-1001', + name: 'winlog.user.identifier', + type: 'keyword', + }, + 'winlog.user.name': { + category: 'winlog', + description: 'Name of the user associated with this event. ', + name: 'winlog.user.name', + type: 'keyword', + }, + 'winlog.user.domain': { + category: 'winlog', + description: 'The domain that the account associated with this event is a member of. ', + name: 'winlog.user.domain', + type: 'keyword', + }, + 'winlog.user.type': { + category: 'winlog', + description: 'The type of account associated with this event. ', + name: 'winlog.user.type', + type: 'keyword', + }, + 'winlog.version': { + category: 'winlog', + description: "The version number of the event's definition.", + name: 'winlog.version', + type: 'long', + }, + activity_id: { + category: 'base', + name: 'activity_id', + type: 'alias', + }, + computer_name: { + category: 'base', + name: 'computer_name', + type: 'alias', + }, + event_id: { + category: 'base', + name: 'event_id', + type: 'alias', + }, + keywords: { + category: 'base', + name: 'keywords', + type: 'alias', + }, + log_name: { + category: 'base', + name: 'log_name', + type: 'alias', + }, + message_error: { + category: 'base', + name: 'message_error', + type: 'alias', + }, + record_number: { + category: 'base', + name: 'record_number', + type: 'alias', + }, + related_activity_id: { + category: 'base', + name: 'related_activity_id', + type: 'alias', + }, + opcode: { + category: 'base', + name: 'opcode', + type: 'alias', + }, + provider_guid: { + category: 'base', + name: 'provider_guid', + type: 'alias', + }, + process_id: { + category: 'base', + name: 'process_id', + type: 'alias', + }, + source_name: { + category: 'base', + name: 'source_name', + type: 'alias', + }, + task: { + category: 'base', + name: 'task', + type: 'alias', + }, + thread_id: { + category: 'base', + name: 'thread_id', + type: 'alias', + }, + 'user.identifier': { + category: 'user', + name: 'user.identifier', + type: 'alias', + }, + 'user.type': { + category: 'user', + name: 'user.type', + type: 'alias', + }, + version: { + category: 'base', + name: 'version', + type: 'alias', + }, + xml: { + category: 'base', + name: 'xml', + type: 'alias', + }, + 'powershell.id': { + category: 'powershell', + description: 'Shell Id.', + example: 'Microsoft Powershell', + name: 'powershell.id', + type: 'keyword', + }, + 'powershell.pipeline_id': { + category: 'powershell', + description: 'Pipeline id.', + example: '1', + name: 'powershell.pipeline_id', + type: 'keyword', + }, + 'powershell.runspace_id': { + category: 'powershell', + description: 'Runspace id.', + example: '4fa9074d-45ab-4e53-9195-e91981ac2bbb', + name: 'powershell.runspace_id', + type: 'keyword', + }, + 'powershell.sequence': { + category: 'powershell', + description: 'Sequence number of the powershell execution.', + example: 1, + name: 'powershell.sequence', + type: 'long', + }, + 'powershell.total': { + category: 'powershell', + description: 'Total number of messages in the sequence.', + example: 10, + name: 'powershell.total', + type: 'long', + }, + 'powershell.command.path': { + category: 'powershell', + description: 'Path of the executed command.', + example: 'C:\\Windows\\system32\\cmd.exe', + name: 'powershell.command.path', + type: 'keyword', + }, + 'powershell.command.name': { + category: 'powershell', + description: 'Name of the executed command.', + example: 'cmd.exe', + name: 'powershell.command.name', + type: 'keyword', + }, + 'powershell.command.type': { + category: 'powershell', + description: 'Type of the executed command.', + example: 'Application', + name: 'powershell.command.type', + type: 'keyword', + }, + 'powershell.command.value': { + category: 'powershell', + description: 'The invoked command.', + example: 'Import-LocalizedData LocalizedData -filename ArchiveResources', + name: 'powershell.command.value', + type: 'text', + }, + 'powershell.command.invocation_details': { + category: 'powershell', + description: 'An array of objects containing detailed information of the executed command. ', + name: 'powershell.command.invocation_details', + type: 'array', + }, + 'powershell.command.invocation_details.type': { + category: 'powershell', + description: 'The type of detail.', + example: 'CommandInvocation', + name: 'powershell.command.invocation_details.type', + type: 'keyword', + }, + 'powershell.command.invocation_details.related_command': { + category: 'powershell', + description: 'The command to which the detail is related to.', + example: 'Add-Type', + name: 'powershell.command.invocation_details.related_command', + type: 'keyword', + }, + 'powershell.command.invocation_details.name': { + category: 'powershell', + description: 'Only used for ParameterBinding detail type. Indicates the parameter name. ', + example: 'AssemblyName', + name: 'powershell.command.invocation_details.name', + type: 'keyword', + }, + 'powershell.command.invocation_details.value': { + category: 'powershell', + description: 'The value of the detail. The meaning of it will depend on the detail type. ', + example: 'System.IO.Compression.FileSystem', + name: 'powershell.command.invocation_details.value', + type: 'text', + }, + 'powershell.connected_user.domain': { + category: 'powershell', + description: 'User domain.', + example: 'VAGRANT', + name: 'powershell.connected_user.domain', + type: 'keyword', + }, + 'powershell.connected_user.name': { + category: 'powershell', + description: 'User name.', + example: 'vagrant', + name: 'powershell.connected_user.name', + type: 'keyword', + }, + 'powershell.engine.version': { + category: 'powershell', + description: 'Version of the PowerShell engine version used to execute the command.', + example: '5.1.17763.1007', + name: 'powershell.engine.version', + type: 'keyword', + }, + 'powershell.engine.previous_state': { + category: 'powershell', + description: 'Previous state of the PowerShell engine. ', + example: 'Available', + name: 'powershell.engine.previous_state', + type: 'keyword', + }, + 'powershell.engine.new_state': { + category: 'powershell', + description: 'New state of the PowerShell engine. ', + example: 'Stopped', + name: 'powershell.engine.new_state', + type: 'keyword', + }, + 'powershell.file.script_block_id': { + category: 'powershell', + description: 'Id of the executed script block.', + example: '50d2dbda-7361-4926-a94d-d9eadfdb43fa', + name: 'powershell.file.script_block_id', + type: 'keyword', + }, + 'powershell.file.script_block_text': { + category: 'powershell', + description: 'Text of the executed script block. ', + example: '.\\a_script.ps1', + name: 'powershell.file.script_block_text', + type: 'text', + }, + 'powershell.process.executable_version': { + category: 'powershell', + description: 'Version of the engine hosting process executable.', + example: '5.1.17763.1007', + name: 'powershell.process.executable_version', + type: 'keyword', + }, + 'powershell.provider.new_state': { + category: 'powershell', + description: 'New state of the PowerShell provider. ', + example: 'Active', + name: 'powershell.provider.new_state', + type: 'keyword', + }, + 'powershell.provider.name': { + category: 'powershell', + description: 'Provider name. ', + example: 'Variable', + name: 'powershell.provider.name', + type: 'keyword', + }, + 'winlog.logon.type': { + category: 'winlog', + description: + 'Logon type name. This is the descriptive version of the `winlog.event_data.LogonType` ordinal. This is an enrichment added by the Security module. ', + example: 'RemoteInteractive', + name: 'winlog.logon.type', + type: 'keyword', + }, + 'winlog.logon.id': { + category: 'winlog', + description: + 'Logon ID that can be used to associate this logon with other events related to the same logon session. ', + name: 'winlog.logon.id', + type: 'keyword', + }, + 'winlog.logon.failure.reason': { + category: 'winlog', + description: 'The reason the logon failed. ', + name: 'winlog.logon.failure.reason', + type: 'keyword', + }, + 'winlog.logon.failure.status': { + category: 'winlog', + description: + 'The reason the logon failed. This is textual description based on the value of the hexadecimal `Status` field. ', + name: 'winlog.logon.failure.status', + type: 'keyword', + }, + 'winlog.logon.failure.sub_status': { + category: 'winlog', + description: + 'Additional information about the logon failure. This is a textual description based on the value of the hexidecimal `SubStatus` field. ', + name: 'winlog.logon.failure.sub_status', + type: 'keyword', + }, + 'sysmon.dns.status': { + category: 'sysmon', + description: 'Windows status code returned for the DNS query.', + name: 'sysmon.dns.status', + type: 'keyword', + }, + 'sysmon.file.archived': { + category: 'sysmon', + description: 'Indicates if the deleted file was archived.', + name: 'sysmon.file.archived', + type: 'boolean', + }, + 'sysmon.file.is_executable': { + category: 'sysmon', + description: 'Indicates if the deleted file was an executable.', + name: 'sysmon.file.is_executable', + type: 'boolean', + }, +}; diff --git a/x-pack/plugins/security_solution/server/utils/beat_schema/index.test.ts b/x-pack/plugins/security_solution/server/utils/beat_schema/index.test.ts deleted file mode 100644 index 29944edf382f4..0000000000000 --- a/x-pack/plugins/security_solution/server/utils/beat_schema/index.test.ts +++ /dev/null @@ -1,397 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { cloneDeep, isArray } from 'lodash/fp'; - -import { convertSchemaToAssociativeArray, getIndexSchemaDoc } from '.'; -import { auditbeatSchema, filebeatSchema, packetbeatSchema } from './8.0.0'; -import { Schema } from './type'; - -describe('Schema Beat', () => { - describe('Transform Schema documentation to an associative array', () => { - test('Auditbeat transformation', async () => { - const convertData: Schema = cloneDeep(auditbeatSchema).slice(0, 1); - convertData[0].fields = isArray(convertData[0].fields) - ? convertData[0].fields!.slice(0, 6) - : []; - - expect(convertSchemaToAssociativeArray(convertData)).toEqual({ - '@timestamp': { - description: - 'Date/time when the event originated.\n\nThis is the date/time extracted from the event, typically representing when\nthe event was generated by the source.\n\nIf the event source has no original timestamp, this value is typically populated\nby the first time the event was received by the pipeline.\n\nRequired field for all events.', - example: '2016-05-23T08:05:34.853Z', - name: '@timestamp', - type: 'date', - }, - labels: { - description: - 'Custom key/value pairs.\n\nCan be used to add meta information to events. Should not contain nested objects.\nAll values are stored as keyword.\n\nExample: `docker` and `k8s` labels.', - example: '{"application": "foo-bar", "env": "production"}', - name: 'labels', - type: 'object', - }, - message: { - description: - 'For log events the message field contains the log message, optimized\nfor viewing in a log viewer.\n\nFor structured logs without an original message field, other fields can be concatenated\nto form a human-readable summary of the event.\n\nIf multiple messages exist, they can be combined into one message.', - example: 'Hello World', - name: 'message', - type: 'text', - }, - tags: { - description: 'List of keywords used to tag each event.', - example: '["production", "env2"]', - name: 'tags', - type: 'keyword', - }, - agent: { - description: - 'The agent fields contain the data about the software entity, if\nany, that collects, detects, or observes events on a host, or takes measurements\non a host.\n\nExamples include Beats. Agents may also run on observers. ECS agent.* fields\nshall be populated with details of the agent running on the host or observer\nwhere the event happened or the measurement was taken.', - name: 'agent', - type: 'group', - fields: { - 'agent.ephemeral_id': { - description: - 'Ephemeral identifier of this agent (if one exists).\n\nThis id normally changes across restarts, but `agent.id` does not.', - example: '8a4f500f', - name: 'ephemeral_id', - type: 'keyword', - }, - 'agent.id': { - description: - 'Unique identifier of this agent (if one exists).\n\nExample: For Beats this would be beat.id.', - example: '8a4f500d', - name: 'id', - type: 'keyword', - }, - 'agent.name': { - description: - 'Custom name of the agent.\n\nThis is a name that can be given to an agent. This can be helpful if for example\ntwo Filebeat instances are running on the same host but a human readable separation\nis needed on which Filebeat instance data is coming from.\n\nIf no name is given, the name is often left empty.', - example: 'foo', - name: 'name', - type: 'keyword', - }, - 'agent.type': { - description: - 'Type of the agent.\n\nThe agent type stays always the same and should be given by the agent used.\nIn case of Filebeat the agent would always be Filebeat also if two Filebeat\ninstances are run on the same machine.', - example: 'filebeat', - name: 'type', - type: 'keyword', - }, - 'agent.version': { - description: 'Version of the agent.', - example: '6.0.0-rc2', - name: 'version', - type: 'keyword', - }, - }, - }, - as: { - description: - 'An autonomous system (AS) is a collection of connected Internet Protocol\n(IP) routing prefixes under the control of one or more network operators on\nbehalf of a single administrative entity or domain that presents a common, clearly\ndefined routing policy to the internet.', - name: 'as', - type: 'group', - fields: { - 'as.number': { - description: - 'Unique number allocated to the autonomous system. The autonomous\nsystem number (ASN) uniquely identifies each network on the Internet.', - example: 15169, - name: 'number', - type: 'long', - }, - 'as.organization.name': { - description: 'Organization name.', - example: 'Google LLC', - name: 'organization.name', - type: 'keyword', - }, - }, - }, - }); - }); - - test('Filebeat transformation', async () => { - const convertData: Schema = cloneDeep(filebeatSchema).slice(0, 1); - convertData[0].fields = isArray(convertData[0].fields) - ? convertData[0].fields!.slice(0, 6) - : []; - - expect(convertSchemaToAssociativeArray(convertData)).toEqual({ - '@timestamp': { - description: - 'Date/time when the event originated.\n\nThis is the date/time extracted from the event, typically representing when\nthe event was generated by the source.\n\nIf the event source has no original timestamp, this value is typically populated\nby the first time the event was received by the pipeline.\n\nRequired field for all events.', - example: '2016-05-23T08:05:34.853Z', - name: '@timestamp', - type: 'date', - }, - labels: { - description: - 'Custom key/value pairs.\n\nCan be used to add meta information to events. Should not contain nested objects.\nAll values are stored as keyword.\n\nExample: `docker` and `k8s` labels.', - example: '{"application": "foo-bar", "env": "production"}', - name: 'labels', - type: 'object', - }, - message: { - description: - 'For log events the message field contains the log message, optimized\nfor viewing in a log viewer.\n\nFor structured logs without an original message field, other fields can be concatenated\nto form a human-readable summary of the event.\n\nIf multiple messages exist, they can be combined into one message.', - example: 'Hello World', - name: 'message', - type: 'text', - }, - tags: { - description: 'List of keywords used to tag each event.', - example: '["production", "env2"]', - name: 'tags', - type: 'keyword', - }, - agent: { - description: - 'The agent fields contain the data about the software entity, if\nany, that collects, detects, or observes events on a host, or takes measurements\non a host.\n\nExamples include Beats. Agents may also run on observers. ECS agent.* fields\nshall be populated with details of the agent running on the host or observer\nwhere the event happened or the measurement was taken.', - name: 'agent', - type: 'group', - fields: { - 'agent.ephemeral_id': { - description: - 'Ephemeral identifier of this agent (if one exists).\n\nThis id normally changes across restarts, but `agent.id` does not.', - example: '8a4f500f', - name: 'ephemeral_id', - type: 'keyword', - }, - 'agent.id': { - description: - 'Unique identifier of this agent (if one exists).\n\nExample: For Beats this would be beat.id.', - example: '8a4f500d', - name: 'id', - type: 'keyword', - }, - 'agent.name': { - description: - 'Custom name of the agent.\n\nThis is a name that can be given to an agent. This can be helpful if for example\ntwo Filebeat instances are running on the same host but a human readable separation\nis needed on which Filebeat instance data is coming from.\n\nIf no name is given, the name is often left empty.', - example: 'foo', - name: 'name', - type: 'keyword', - }, - 'agent.type': { - description: - 'Type of the agent.\n\nThe agent type stays always the same and should be given by the agent used.\nIn case of Filebeat the agent would always be Filebeat also if two Filebeat\ninstances are run on the same machine.', - example: 'filebeat', - name: 'type', - type: 'keyword', - }, - 'agent.version': { - description: 'Version of the agent.', - example: '6.0.0-rc2', - name: 'version', - type: 'keyword', - }, - }, - }, - as: { - description: - 'An autonomous system (AS) is a collection of connected Internet Protocol\n(IP) routing prefixes under the control of one or more network operators on\nbehalf of a single administrative entity or domain that presents a common, clearly\ndefined routing policy to the internet.', - name: 'as', - type: 'group', - fields: { - 'as.number': { - description: - 'Unique number allocated to the autonomous system. The autonomous\nsystem number (ASN) uniquely identifies each network on the Internet.', - example: 15169, - name: 'number', - type: 'long', - }, - 'as.organization.name': { - description: 'Organization name.', - example: 'Google LLC', - name: 'organization.name', - type: 'keyword', - }, - }, - }, - }); - }); - - test('Packetbeat transformation', async () => { - const convertData: Schema = cloneDeep(packetbeatSchema).slice(0, 1); - convertData[0].fields = isArray(convertData[0].fields) - ? convertData[0].fields!.slice(0, 6) - : []; - - expect(convertSchemaToAssociativeArray(convertData)).toEqual({ - '@timestamp': { - description: - 'Date/time when the event originated.\n\nThis is the date/time extracted from the event, typically representing when\nthe event was generated by the source.\n\nIf the event source has no original timestamp, this value is typically populated\nby the first time the event was received by the pipeline.\n\nRequired field for all events.', - example: '2016-05-23T08:05:34.853Z', - name: '@timestamp', - type: 'date', - }, - labels: { - description: - 'Custom key/value pairs.\n\nCan be used to add meta information to events. Should not contain nested objects.\nAll values are stored as keyword.\n\nExample: `docker` and `k8s` labels.', - example: '{"application": "foo-bar", "env": "production"}', - name: 'labels', - type: 'object', - }, - message: { - description: - 'For log events the message field contains the log message, optimized\nfor viewing in a log viewer.\n\nFor structured logs without an original message field, other fields can be concatenated\nto form a human-readable summary of the event.\n\nIf multiple messages exist, they can be combined into one message.', - example: 'Hello World', - name: 'message', - type: 'text', - }, - tags: { - description: 'List of keywords used to tag each event.', - example: '["production", "env2"]', - name: 'tags', - type: 'keyword', - }, - agent: { - description: - 'The agent fields contain the data about the software entity, if\nany, that collects, detects, or observes events on a host, or takes measurements\non a host.\n\nExamples include Beats. Agents may also run on observers. ECS agent.* fields\nshall be populated with details of the agent running on the host or observer\nwhere the event happened or the measurement was taken.', - name: 'agent', - type: 'group', - fields: { - 'agent.ephemeral_id': { - description: - 'Ephemeral identifier of this agent (if one exists).\n\nThis id normally changes across restarts, but `agent.id` does not.', - example: '8a4f500f', - name: 'ephemeral_id', - type: 'keyword', - }, - 'agent.id': { - description: - 'Unique identifier of this agent (if one exists).\n\nExample: For Beats this would be beat.id.', - example: '8a4f500d', - name: 'id', - type: 'keyword', - }, - 'agent.name': { - description: - 'Custom name of the agent.\n\nThis is a name that can be given to an agent. This can be helpful if for example\ntwo Filebeat instances are running on the same host but a human readable separation\nis needed on which Filebeat instance data is coming from.\n\nIf no name is given, the name is often left empty.', - example: 'foo', - name: 'name', - type: 'keyword', - }, - 'agent.type': { - description: - 'Type of the agent.\n\nThe agent type stays always the same and should be given by the agent used.\nIn case of Filebeat the agent would always be Filebeat also if two Filebeat\ninstances are run on the same machine.', - example: 'filebeat', - name: 'type', - type: 'keyword', - }, - 'agent.version': { - description: 'Version of the agent.', - example: '6.0.0-rc2', - name: 'version', - type: 'keyword', - }, - }, - }, - as: { - description: - 'An autonomous system (AS) is a collection of connected Internet Protocol\n(IP) routing prefixes under the control of one or more network operators on\nbehalf of a single administrative entity or domain that presents a common, clearly\ndefined routing policy to the internet.', - name: 'as', - type: 'group', - fields: { - 'as.number': { - description: - 'Unique number allocated to the autonomous system. The autonomous\nsystem number (ASN) uniquely identifies each network on the Internet.', - example: 15169, - name: 'number', - type: 'long', - }, - 'as.organization.name': { - description: 'Organization name.', - example: 'Google LLC', - name: 'organization.name', - type: 'keyword', - }, - }, - }, - }); - }); - }); - - describe('GetIndexSchemaDoc', () => { - test('Filebeat transformation', async () => { - expect(Object.keys(getIndexSchemaDoc('auditbeat'))).toEqual([ - '_id', - '_index', - '@timestamp', - 'labels', - 'message', - 'tags', - 'agent', - 'as', - 'client', - 'cloud', - 'code_signature', - 'container', - 'destination', - 'dll', - 'dns', - 'ecs', - 'error', - 'event', - 'file', - 'geo', - 'group', - 'hash', - 'host', - 'http', - 'interface', - 'log', - 'network', - 'observer', - 'organization', - 'os', - 'package', - 'pe', - 'process', - 'registry', - 'related', - 'rule', - 'server', - 'service', - 'source', - 'threat', - 'tls', - 'tracing', - 'url', - 'user', - 'user_agent', - 'vlan', - 'vulnerability', - 'agent.hostname', - 'beat.timezone', - 'fields', - 'beat.name', - 'beat.hostname', - 'timeseries.instance', - 'cloud.project.id', - 'cloud.image.id', - 'meta.cloud.provider', - 'meta.cloud.instance_id', - 'meta.cloud.instance_name', - 'meta.cloud.machine_type', - 'meta.cloud.availability_zone', - 'meta.cloud.project_id', - 'meta.cloud.region', - 'docker', - 'kubernetes', - 'jolokia.agent.version', - 'jolokia.agent.id', - 'jolokia.server.product', - 'jolokia.server.version', - 'jolokia.server.vendor', - 'jolokia.url', - 'jolokia.secured', - 'auditd', - 'geoip', - 'socket', - 'system.audit', - ]); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/server/utils/beat_schema/index.ts b/x-pack/plugins/security_solution/server/utils/beat_schema/index.ts deleted file mode 100644 index 58627a199a181..0000000000000 --- a/x-pack/plugins/security_solution/server/utils/beat_schema/index.ts +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { get, has, isArray, isEmpty, isNumber, isString, memoize, pick } from 'lodash/fp'; - -import { - auditbeatSchema, - baseCategoryFields, - ecsSchema, - extraSchemaField, - filebeatSchema, - packetbeatSchema, - winlogbeatSchema, -} from './8.0.0'; -import { OutputSchema, Schema, SchemaFields, SchemaItem } from './type'; - -export * from './type'; -export { baseCategoryFields }; -export const convertSchemaToAssociativeArray = (schema: Schema): OutputSchema => - schema.reduce((accumulator: OutputSchema, item: Partial) => { - if (item.fields != null && !isEmpty(item.fields)) { - return { - ...accumulator, - ...convertFieldsToAssociativeArray(item), - }; - } - return accumulator; - }, {}); - -const paramsToPick = ['description', 'example', 'name', 'type', 'format']; - -const onlyStringOrNumber = (fields: object) => - Object.keys(fields).reduce((acc, item) => { - const value = get(item, fields); - return { - ...acc, - [item]: isString(value) || isNumber(value) ? value : JSON.stringify(value), - }; - }, {}); - -const convertFieldsToAssociativeArray = ( - schemaFields: Partial, - path: string = '' -): OutputSchema => - schemaFields.fields && isArray(schemaFields.fields) - ? schemaFields.fields.reduce((accumulator: OutputSchema, item: Partial) => { - if (item.name) { - const attr = isEmpty(path) ? item.name : `${path}.${item.name}`; - if (!isEmpty(item.fields) && isEmpty(path)) { - return { - ...accumulator, - [attr]: { - ...onlyStringOrNumber(pick(paramsToPick, item)), - fields: { - ...convertFieldsToAssociativeArray(item, attr), - }, - }, - }; - } else if (!isEmpty(item.fields) && !isEmpty(path)) { - return { - ...accumulator, - [attr]: onlyStringOrNumber(pick(paramsToPick, item)), - ...convertFieldsToAssociativeArray(item, attr), - }; - } else { - return { - ...accumulator, - [attr]: onlyStringOrNumber(pick(paramsToPick, item)), - }; - } - } - return accumulator; - }, {}) - : {}; - -export const getIndexSchemaDoc = memoize((index: string) => { - if (index.match('auditbeat') != null) { - return { - ...extraSchemaField, - ...convertSchemaToAssociativeArray(auditbeatSchema), - }; - } else if (index.match('filebeat') != null) { - return { - ...extraSchemaField, - ...convertSchemaToAssociativeArray(filebeatSchema), - }; - } else if (index.match('packetbeat') != null) { - return { - ...extraSchemaField, - ...convertSchemaToAssociativeArray(packetbeatSchema), - }; - } else if (index.match('winlogbeat') != null) { - return { - ...extraSchemaField, - ...convertSchemaToAssociativeArray(winlogbeatSchema), - }; - } - return { - ...extraSchemaField, - ...convertSchemaToAssociativeArray(ecsSchema), - }; -}); - -export const hasDocumentation = (index: string, path: string): boolean => { - const splitPath = path.split('.'); - const category = splitPath.length > 0 ? splitPath[0] : null; - if (category === null) { - return false; - } - if (splitPath.length > 1) { - return has([category, 'fields', path], getIndexSchemaDoc(index)); - } - return has(category, getIndexSchemaDoc(index)); -}; - -export const getDocumentation = (index: string, path: string) => { - const splitPath = path.split('.'); - const category = splitPath.length > 0 ? splitPath[0] : null; - if (category === null) { - return {}; - } - if (splitPath.length > 1) { - return get([category, 'fields', path], getIndexSchemaDoc(index)) || {}; - } - return get(category, getIndexSchemaDoc(index)) || {}; -}; diff --git a/x-pack/plugins/security_solution/server/utils/beat_schema/type.ts b/x-pack/plugins/security_solution/server/utils/beat_schema/type.ts deleted file mode 100644 index 722589ce7e2bb..0000000000000 --- a/x-pack/plugins/security_solution/server/utils/beat_schema/type.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -/* - * BEAT Interface - * - */ - -export interface SchemaFields { - default_field: boolean; - default_fields: boolean; - definition: string; - deprecated: string; - description: string; - doc_values: boolean; - example: string | number | object | boolean; - footnote: string; - format: string; - group: number; - index: boolean; - ignore_above: number; - input_format: string; - level: string; - migration: boolean; - multi_fields: object[]; - name: string; - norms: boolean; - object_type: string; - object_type_mapping_type: string; - output_format: string; - output_precision: number; - overwrite: boolean; - path: string; - possible_values: string[] | number[]; - release: string; - required: boolean; - reusable: object; - short: string; - title: string; - type: string; - fields: Array>; -} - -export interface SchemaItem { - anchor: string; - key: string; - title: string; - description: string; - short_config: boolean; - release: string; - fields: Array>; -} - -export type Schema = Array>; - -/* - * Associative Array Output Interface - * - */ - -export interface RequiredSchemaField { - description: string; - example: string | number; - name: string; - type: string; - format: string; - fields: Readonly>>; -} - -export type OutputSchema = Readonly>>; diff --git a/x-pack/plugins/spaces/server/lib/spaces_client/spaces_client.ts b/x-pack/plugins/spaces/server/lib/spaces_client/spaces_client.ts index acb00a87bf7d9..5ef0b5375d796 100644 --- a/x-pack/plugins/spaces/server/lib/spaces_client/spaces_client.ts +++ b/x-pack/plugins/spaces/server/lib/spaces_client/spaces_client.ts @@ -104,7 +104,7 @@ export class SpacesClient { `SpacesClient.getAll(), using RBAC. returning 403/Forbidden. Not authorized for any spaces for ${purpose} purpose.` ); this.auditLogger.spacesAuthorizationFailure(username, 'getAll'); - throw Boom.forbidden(); + throw Boom.forbidden(); // Note: there is a catch for this in `SpacesSavedObjectsClient.find`; if we get rid of this error, remove that too } this.auditLogger.spacesAuthorizationSuccess(username, 'getAll', authorized as string[]); diff --git a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts index c9c17d091cd55..f7621f11a1c05 100644 --- a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts +++ b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts @@ -10,6 +10,8 @@ import { spacesServiceMock } from '../spaces_service/spaces_service.mock'; import { savedObjectsClientMock } from '../../../../../src/core/server/mocks'; import { SavedObjectTypeRegistry } from 'src/core/server'; import { SpacesClient } from '../lib/spaces_client'; +import { spacesClientMock } from '../lib/spaces_client/spaces_client.mock'; +import Boom from 'boom'; const typeRegistry = new SavedObjectTypeRegistry(); typeRegistry.registerType({ @@ -129,6 +131,34 @@ const ERROR_NAMESPACE_SPECIFIED = 'Spaces currently determines the namespaces'; }); describe('#find', () => { + const EMPTY_RESPONSE = { saved_objects: [], total: 0, per_page: 20, page: 1 }; + + test(`returns empty result if user is unauthorized in this space`, async () => { + const { client, baseClient, spacesService } = await createSpacesSavedObjectsClient(); + const spacesClient = spacesClientMock.create(); + spacesClient.getAll.mockResolvedValue([]); + spacesService.scopedClient.mockResolvedValue(spacesClient); + + const options = Object.freeze({ type: 'foo', namespaces: ['some-ns'] }); + const actualReturnValue = await client.find(options); + + expect(actualReturnValue).toEqual(EMPTY_RESPONSE); + expect(baseClient.find).not.toHaveBeenCalled(); + }); + + test(`returns empty result if user is unauthorized in any space`, async () => { + const { client, baseClient, spacesService } = await createSpacesSavedObjectsClient(); + const spacesClient = spacesClientMock.create(); + spacesClient.getAll.mockRejectedValue(Boom.unauthorized()); + spacesService.scopedClient.mockResolvedValue(spacesClient); + + const options = Object.freeze({ type: 'foo', namespaces: ['some-ns'] }); + const actualReturnValue = await client.find(options); + + expect(actualReturnValue).toEqual(EMPTY_RESPONSE); + expect(baseClient.find).not.toHaveBeenCalled(); + }); + test(`passes options.type to baseClient if valid singular type specified`, async () => { const { client, baseClient } = await createSpacesSavedObjectsClient(); const expectedReturnValue = { diff --git a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts index 4e830d6149537..a65e0431aef92 100644 --- a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts +++ b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import Boom from 'boom'; import { SavedObjectsBaseOptions, SavedObjectsBulkCreateObject, @@ -16,8 +17,9 @@ import { SavedObjectsUpdateOptions, SavedObjectsAddToNamespacesOptions, SavedObjectsDeleteFromNamespacesOptions, + SavedObjectsUtils, ISavedObjectTypeRegistry, -} from 'src/core/server'; +} from '../../../../../src/core/server'; import { SpacesServiceSetup } from '../spaces_service/spaces_service'; import { spaceIdToNamespace } from '../lib/utils/namespace'; import { SpacesClient } from '../lib/spaces_client'; @@ -164,19 +166,26 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { let namespaces = options.namespaces; if (namespaces) { const spacesClient = await this.getSpacesClient; - const availableSpaces = await spacesClient.getAll('findSavedObjects'); - if (namespaces.includes('*')) { - namespaces = availableSpaces.map((space) => space.id); - } else { - namespaces = namespaces.filter((namespace) => - availableSpaces.some((space) => space.id === namespace) - ); - } - // This forbidden error allows this scenario to be consistent - // with the way the SpacesClient behaves when no spaces are authorized - // there. - if (namespaces.length === 0) { - throw this.errors.decorateForbiddenError(new Error()); + + try { + const availableSpaces = await spacesClient.getAll('findSavedObjects'); + if (namespaces.includes('*')) { + namespaces = availableSpaces.map((space) => space.id); + } else { + namespaces = namespaces.filter((namespace) => + availableSpaces.some((space) => space.id === namespace) + ); + } + if (namespaces.length === 0) { + // return empty response, since the user is unauthorized in this space (or these spaces), but we don't return forbidden errors for `find` operations + return SavedObjectsUtils.createEmptyFindResponse(options); + } + } catch (err) { + if (Boom.isBoom(err) && err.output.payload.statusCode === 403) { + // return empty response, since the user is unauthorized in any space, but we don't return forbidden errors for `find` operations + return SavedObjectsUtils.createEmptyFindResponse(options); + } + throw err; } } else { namespaces = [this.spaceId]; diff --git a/x-pack/plugins/transform/common/constants.ts b/x-pack/plugins/transform/common/constants.ts index 5efb6f31c1e3f..f2dbc085ab46f 100644 --- a/x-pack/plugins/transform/common/constants.ts +++ b/x-pack/plugins/transform/common/constants.ts @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; -import { LICENSE_TYPE_BASIC, LicenseType } from '../../../legacy/common/constants'; +import { LicenseType } from '../../licensing/common/types'; export const DEFAULT_REFRESH_INTERVAL_MS = 30000; export const MINIMUM_REFRESH_INTERVAL_MS = 1000; @@ -14,7 +14,7 @@ export const PROGRESS_REFRESH_INTERVAL_MS = 2000; export const PLUGIN = { ID: 'transform', - MINIMUM_LICENSE_REQUIRED: LICENSE_TYPE_BASIC as LicenseType, + MINIMUM_LICENSE_REQUIRED: 'basic' as LicenseType, getI18nName: (): string => { return i18n.translate('xpack.transform.appName', { defaultMessage: 'Transforms', 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 fa601bbaed91e..418dcf78eda5c 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 @@ -43,6 +43,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'; export interface StepDetailsExposedState { created: boolean; @@ -83,7 +84,6 @@ export const StepCreateForm: FC = React.memo( const deps = useAppDependencies(); const indexPatterns = deps.data.indexPatterns; - const uiSettings = deps.uiSettings; const toastNotifications = useToastNotifications(); useEffect(() => { @@ -189,35 +189,14 @@ export const StepCreateForm: FC = React.memo( const indexPatternName = transformConfig.dest.index; try { - const newIndexPattern = await indexPatterns.make(); - - Object.assign(newIndexPattern, { - id: '', - title: indexPatternName, - timeFieldName, - }); - const id = await newIndexPattern.create(); - - await indexPatterns.clearCache(); - - // id returns false if there's a duplicate index pattern. - if (id === false) { - toastNotifications.addDanger( - i18n.translate('xpack.transform.stepCreateForm.duplicateIndexPatternErrorMessage', { - defaultMessage: - 'An error occurred creating the Kibana index pattern {indexPatternName}: The index pattern already exists.', - values: { indexPatternName }, - }) - ); - setLoading(false); - return; - } - - // check if there's a default index pattern, if not, - // set the newly created one as the default index pattern. - if (!uiSettings.get('defaultIndex')) { - await uiSettings.set('defaultIndex', id); - } + const newIndexPattern = await indexPatterns.createAndSave( + { + title: indexPatternName, + timeFieldName, + }, + false, + true + ); toastNotifications.addSuccess( i18n.translate('xpack.transform.stepCreateForm.createIndexPatternSuccessMessage', { @@ -226,22 +205,32 @@ export const StepCreateForm: FC = React.memo( }) ); - setIndexPatternId(id); + setIndexPatternId(newIndexPattern.id); setLoading(false); return true; } catch (e) { - toastNotifications.addDanger({ - title: i18n.translate('xpack.transform.stepCreateForm.createIndexPatternErrorMessage', { - defaultMessage: - 'An error occurred creating the Kibana index pattern {indexPatternName}:', - values: { indexPatternName }, - }), - text: toMountPoint( - - ), - }); - setLoading(false); - return false; + if (e instanceof DuplicateIndexPatternError) { + toastNotifications.addDanger( + i18n.translate('xpack.transform.stepCreateForm.duplicateIndexPatternErrorMessage', { + defaultMessage: + 'An error occurred creating the Kibana index pattern {indexPatternName}: The index pattern already exists.', + values: { indexPatternName }, + }) + ); + } else { + toastNotifications.addDanger({ + title: i18n.translate('xpack.transform.stepCreateForm.createIndexPatternErrorMessage', { + defaultMessage: + 'An error occurred creating the Kibana index pattern {indexPatternName}:', + values: { indexPatternName }, + }), + text: toMountPoint( + + ), + }); + setLoading(false); + return false; + } } }; diff --git a/x-pack/plugins/transform/server/routes/api/error_utils.ts b/x-pack/plugins/transform/server/routes/api/error_utils.ts index 269cd28c4bda6..ef5927651df88 100644 --- a/x-pack/plugins/transform/server/routes/api/error_utils.ts +++ b/x-pack/plugins/transform/server/routes/api/error_utils.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { boomify, isBoom } from 'boom'; +import Boom from 'boom'; import { i18n } from '@kbn/i18n'; @@ -76,10 +76,65 @@ export function fillResultsWithTimeouts({ results, id, items, action }: Params) } export function wrapError(error: any): CustomHttpResponseOptions { - const boom = isBoom(error) ? error : boomify(error, { statusCode: error.status }); + const boom = Boom.isBoom(error) ? error : Boom.boomify(error, { statusCode: error.status }); return { body: boom, headers: boom.output.headers, statusCode: boom.output.statusCode, }; } + +function extractCausedByChain( + causedBy: Record = {}, + accumulator: string[] = [] +): string[] { + const { reason, caused_by } = causedBy; // eslint-disable-line @typescript-eslint/naming-convention + + if (reason) { + accumulator.push(reason); + } + + if (caused_by) { + return extractCausedByChain(caused_by, accumulator); + } + + return accumulator; +} + +/** + * Wraps an error thrown by the ES JS client into a Boom error response and returns it + * + * @param err Object Error thrown by ES JS client + * @param statusCodeToMessageMap Object Optional map of HTTP status codes => error messages + * @return Object Boom error response + */ +export function wrapEsError(err: any, statusCodeToMessageMap: Record = {}) { + const { statusCode, response } = err; + + const { + error: { + root_cause = [], // eslint-disable-line @typescript-eslint/naming-convention + caused_by = {}, // eslint-disable-line @typescript-eslint/naming-convention + } = {}, + } = JSON.parse(response); + + // If no custom message if specified for the error's status code, just + // wrap the error as a Boom error response, include the additional information from ES, and return it + if (!statusCodeToMessageMap[statusCode]) { + const boomError = Boom.boomify(err, { statusCode }); + + // The caused_by chain has the most information so use that if it's available. If not then + // settle for the root_cause. + const causedByChain = extractCausedByChain(caused_by); + const defaultCause = root_cause.length ? extractCausedByChain(root_cause[0]) : undefined; + + // @ts-expect-error cause is not defined on payload type + boomError.output.payload.cause = causedByChain.length ? causedByChain : defaultCause; + return boomError; + } + + // Otherwise, use the custom message to create a Boom error response and + // return it + const message = statusCodeToMessageMap[statusCode]; + return new Boom(message, { statusCode }); +} diff --git a/x-pack/plugins/transform/server/routes/api/field_histograms.ts b/x-pack/plugins/transform/server/routes/api/field_histograms.ts index 88352ec4af129..636af095e0053 100644 --- a/x-pack/plugins/transform/server/routes/api/field_histograms.ts +++ b/x-pack/plugins/transform/server/routes/api/field_histograms.ts @@ -9,8 +9,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { wrapEsError } from '../../../../../legacy/server/lib/create_router/error_wrappers'; - import { indexPatternTitleSchema, IndexPatternTitleSchema, @@ -24,7 +22,7 @@ import { RouteDependencies } from '../../types'; import { addBasePath } from '../index'; -import { wrapError } from './error_utils'; +import { wrapError, wrapEsError } from './error_utils'; export function registerFieldHistogramsRoutes({ router, license }: RouteDependencies) { router.post( diff --git a/x-pack/plugins/transform/server/routes/api/transforms.ts b/x-pack/plugins/transform/server/routes/api/transforms.ts index 3d2018eb5801f..31b2c2285a764 100644 --- a/x-pack/plugins/transform/server/routes/api/transforms.ts +++ b/x-pack/plugins/transform/server/routes/api/transforms.ts @@ -12,7 +12,6 @@ import { SavedObjectsClientContract, LegacyAPICaller, } from 'kibana/server'; -import { wrapEsError } from '../../../../../legacy/server/lib/create_router/error_wrappers'; import { TRANSFORM_STATE } from '../../../common/constants'; import { TransformId } from '../../../common/types/transform'; @@ -54,7 +53,7 @@ import { RouteDependencies } from '../../types'; import { addBasePath } from '../index'; -import { isRequestTimeout, fillResultsWithTimeouts, wrapError } from './error_utils'; +import { isRequestTimeout, fillResultsWithTimeouts, wrapError, wrapEsError } from './error_utils'; import { registerTransformsAuditMessagesRoutes } from './transforms_audit_messages'; import { IIndexPattern } from '../../../../../../src/plugins/data/common/index_patterns'; diff --git a/x-pack/plugins/transform/server/routes/api/transforms_audit_messages.ts b/x-pack/plugins/transform/server/routes/api/transforms_audit_messages.ts index f01b2bdb73fd5..20cb6ffb4978b 100644 --- a/x-pack/plugins/transform/server/routes/api/transforms_audit_messages.ts +++ b/x-pack/plugins/transform/server/routes/api/transforms_audit_messages.ts @@ -6,13 +6,12 @@ import { transformIdParamSchema, TransformIdParamSchema } from '../../../common/api_schemas/common'; import { AuditMessage, TransformMessage } from '../../../common/types/messages'; -import { wrapEsError } from '../../../../../legacy/server/lib/create_router/error_wrappers'; import { RouteDependencies } from '../../types'; import { addBasePath } from '../index'; -import { wrapError } from './error_utils'; +import { wrapError, wrapEsError } from './error_utils'; const ML_DF_NOTIFICATION_INDEX_PATTERN = '.transform-notifications-read'; const SIZE = 500; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 790d1152f0846..ed66d56d552a5 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -801,8 +801,6 @@ "data.indexPatterns.ensureDefaultIndexPattern.bannerLabel": "Kibanaでデータの可視化と閲覧を行うには、Elasticsearchからデータを取得するためのインデックスパターンの作成が必要です。", "data.indexPatterns.fetchFieldErrorTitle": "インデックスパターンのフィールド取得中にエラーが発生 {title} (ID: {id})", "data.indexPatterns.unableWriteLabel": "インデックスパターンを書き込めません!このインデックスパターンへの最新の変更を取得するには、ページを更新してください。", - "data.indexPatterns.unknownFieldErrorMessage": "インデックスパターン「{title}」のフィールド「{name}」が不明なフィールドタイプを使用しています。", - "data.indexPatterns.unknownFieldHeader": "不明なフィールドタイプ {type}", "data.noDataPopover.content": "この時間範囲にはデータが含まれていません表示するフィールドを増やし、グラフを作成するには、時間範囲を広げるか、調整してください。", "data.noDataPopover.dismissAction": "今後表示しない", "data.noDataPopover.subtitle": "ヒント", @@ -4806,8 +4804,6 @@ "xpack.apm.serviceMap.avgMemoryUsagePopoverStat": "メモリー使用状況(平均)", "xpack.apm.serviceMap.avgReqPerMinutePopoverMetric": "1分あたりのリクエスト(平均)", "xpack.apm.serviceMap.avgTransDurationPopoverStat": "トランザクションの長さ(平均)", - "xpack.apm.serviceMap.betaBadge": "ベータ", - "xpack.apm.serviceMap.betaTooltipMessage": "現在、この機能はベータです。不具合を見つけた場合やご意見がある場合、サポートに問い合わせるか、またはディスカッションフォーラムにご報告ください。", "xpack.apm.serviceMap.center": "中央", "xpack.apm.serviceMap.download": "ダウンロード", "xpack.apm.serviceMap.emptyBanner.docsLink": "詳細はドキュメントをご覧ください", @@ -8817,7 +8813,6 @@ "xpack.infra.registerFeatures.logsDescription": "ログをリアルタイムでストリーするか、コンソール式の UI で履歴ビューをスクロールします。", "xpack.infra.registerFeatures.logsTitle": "ログ", "xpack.infra.sampleDataLinkLabel": "ログ", - "xpack.infra.savedView.changeView": "ビューの変更", "xpack.infra.savedView.currentView": "現在のビュー", "xpack.infra.savedView.defaultViewNameHosts": "デフォルトビュー", "xpack.infra.savedView.errorOnCreate.duplicateViewName": "その名前のビューは既に存在します", @@ -9291,15 +9286,7 @@ "xpack.ingestManager.setupPage.missingRequirementsKibanaTitle": "Kibana構成で、次の項目を有効にします。", "xpack.ingestManager.setupPage.tlsFlagText": "{kibanaSecurityLink}.{securityFlag}を{true}に設定します。開発目的では、危険な代替として{tlsFlag}を{true}に設定して、{tlsLink}を無効化できます。", "xpack.ingestManager.setupPage.tlsLink": "TLS", - "xpack.ingestManager.unenrollAgents.confirmModal.cancelButtonLabel": "キャンセル", - "xpack.ingestManager.unenrollAgents.confirmModal.confirmButtonLabel": "登録解除", - "xpack.ingestManager.unenrollAgents.confirmModal.deleteMultipleTitle": "{count, plural, one {# エージェント} other {# エージェント}}の登録を解除しますか?", - "xpack.ingestManager.unenrollAgents.confirmModal.deleteSingleTitle": "エージェント「{id}」の登録を解除しますか?", - "xpack.ingestManager.unenrollAgents.confirmModal.forceDeleteSingleTitle": "強制的にエージェント「{id}」の登録を解除しますか?", - "xpack.ingestManager.unenrollAgents.confirmModal.loadingButtonLabel": "読み込み中...", - "xpack.ingestManager.unenrollAgents.fatalErrorNotificationTitle": "エージェントの登録解除エラー", - "xpack.ingestManager.unenrollAgents.successForceSingleNotificationTitle": "エージェント「{id}」の登録を解除しました", - "xpack.ingestManager.unenrollAgents.successSingleNotificationTitle": "エージェント「{id}」を登録解除しています", + "xpack.ingestManager.unenrollAgents.cancelButtonLabel": "キャンセル", "xpack.ingestPipelines.app.checkingPrivilegesDescription": "権限を確認中…", "xpack.ingestPipelines.app.checkingPrivilegesErrorMessage": "サーバーからユーザー特権を取得中にエラーが発生。", "xpack.ingestPipelines.app.deniedPrivilegeDescription": "Ingest Pipelinesを使用するには、{privilegesCount, plural, one {このクラスター特権} other {これらのクラスター特権}}が必要です:{missingPrivileges}。", @@ -10529,8 +10516,6 @@ "xpack.ml.calendarsList.deleteCalendars.deletingCalendarSuccessNotificationMessage": "{messageId} が選択されました", "xpack.ml.calendarsList.deleteCalendarsModal.cancelButtonLabel": "キャンセル", "xpack.ml.calendarsList.deleteCalendarsModal.deleteButtonLabel": "削除", - "xpack.ml.calendarsList.deleteCalendarsModal.deleteCalendarsDescription": "{calendarsCount, plural, one {このカレンダー} other {これらのカレンダー}}を削除しますか?{calendarsList}", - "xpack.ml.calendarsList.deleteCalendarsModal.deleteCalendarTitle": "カレンダーの削除", "xpack.ml.calendarsList.errorWithLoadingListOfCalendarsErrorMessage": "カレンダーのリストの読み込み中にエラーが発生しました。", "xpack.ml.calendarsList.table.allJobsLabel": "すべてのジョブに適用", "xpack.ml.calendarsList.table.deleteButtonLabel": "削除", @@ -12589,7 +12574,6 @@ "xpack.monitoring.cluster.overview.esPanel.versionLabel": "バージョン", "xpack.monitoring.cluster.overview.esPanel.versionNotAvailableDescription": "N/A", "xpack.monitoring.cluster.overview.esPanel.warnLogsTooltipText": "警告ログの数です", - "xpack.monitoring.cluster.overview.healthStatusDescription": "ヘルス: {status}", "xpack.monitoring.cluster.overview.kibanaPanel.connectionsLabel": "接続", "xpack.monitoring.cluster.overview.kibanaPanel.instancesCountLinkAriaLabel": "Kibana インスタンス: {instancesCount}", "xpack.monitoring.cluster.overview.kibanaPanel.instancesCountLinkLabel": "インスタンス: {instancesCount}", @@ -12708,8 +12692,6 @@ "xpack.monitoring.elasticsearch.nodeDetailStatus.transportAddress": "トランスポートアドレス", "xpack.monitoring.elasticsearch.nodeDetailStatus.typeLabel": "タイプ", "xpack.monitoring.elasticsearch.nodes.alertsColumnTitle": "アラート", - "xpack.monitoring.elasticsearch.nodes.cells.maxText": "最高 {metric}", - "xpack.monitoring.elasticsearch.nodes.cells.minText": "最低 {metric}", "xpack.monitoring.elasticsearch.nodes.cpuThrottlingColumnTitle": "CPU スロットル", "xpack.monitoring.elasticsearch.nodes.cpuUsageColumnTitle": "CPU 使用状況", "xpack.monitoring.elasticsearch.nodes.diskFreeSpaceColumnTitle": "ディスクの空き容量", @@ -14890,7 +14872,7 @@ "xpack.securitySolution.auditd.violatedSeLinuxPolicyDescription": "selinuxポリシーに違反しました", "xpack.securitySolution.auditd.wasAuthorizedToUseDescription": "が以下の使用を承認されました。", "xpack.securitySolution.auditd.withResultDescription": "結果付き", - "xpack.securitySolution.authenticationsTable.authenticationFailures": "認証", + "xpack.securitySolution.authenticationsTable.authentications": "認証", "xpack.securitySolution.authenticationsTable.failures": "失敗", "xpack.securitySolution.authenticationsTable.lastFailedDestination": "前回失敗したデスティネーション", "xpack.securitySolution.authenticationsTable.lastFailedSource": "前回失敗したソース", @@ -15893,19 +15875,14 @@ "xpack.securitySolution.endpoint.resolver.panel.error.goBack": "このリンクをクリックすると、すべてのプロセスのリストに戻ります。", "xpack.securitySolution.endpoint.resolver.panel.processDescList.events": "イベント", "xpack.securitySolution.endpoint.resolver.panel.processEventCounts.events": "イベント", - "xpack.securitySolution.endpoint.resolver.panel.processEventListByType.eventDescriptiveName": "{descriptor} {subject}", "xpack.securitySolution.endpoint.resolver.panel.processEventListByType.events": "イベント", "xpack.securitySolution.endpoint.resolver.panel.relatedCounts.numberOfEventsInCrumb": "{totalCount}件のイベント", - "xpack.securitySolution.endpoint.resolver.panel.relatedDetail.missing": "関連イベントが見つかりません。", "xpack.securitySolution.endpoint.resolver.panel.relatedDetail.wait": "イベントを待機しています...", "xpack.securitySolution.endpoint.resolver.panel.relatedEventDetail.atTime": "@ {date}", "xpack.securitySolution.endpoint.resolver.panel.relatedEventDetail.categoryAndType": "{category} {eventType}", "xpack.securitySolution.endpoint.resolver.panel.relatedEventDetail.countByCategory": "{count} {category}", "xpack.securitySolution.endpoint.resolver.panel.relatedEventDetail.detailsForProcessName": "詳細:{processName}", - "xpack.securitySolution.endpoint.resolver.panel.relatedEventDetail.eventDescriptiveName": "{descriptor} {subject}", - "xpack.securitySolution.endpoint.resolver.panel.relatedEventDetail.eventDescriptiveNameInTitle": "{descriptor} {subject}", "xpack.securitySolution.endpoint.resolver.panel.relatedEventDetail.events": "イベント", - "xpack.securitySolution.endpoint.resolver.panel.relatedEventDetail.NA": "N/A", "xpack.securitySolution.endpoint.resolver.panel.relatedEventDetail.numberOfEvents": "{totalCount}件のイベント", "xpack.securitySolution.endpoint.resolver.panel.relatedEventList.countByCategory": "{count} {category}", "xpack.securitySolution.endpoint.resolver.panel.relatedEventList.numberOfEvents": "{totalCount}件のイベント", @@ -16688,9 +16665,6 @@ "xpack.securitySolution.zeek.sfDescription": "通常のSYN/FIN完了", "xpack.securitySolution.zeek.shDescription": "接続元がFINに続きSYNを送信しました。レスポンダーからSYN-ACKはありません", "xpack.securitySolution.zeek.shrDescription": "レスポンダーがFINに続きSYNを送信しました。接続元からSYN-ACKはありません", - "xpack.server.checkLicense.errorExpiredMessage": "{licenseType} ライセンスが期限切れのため {pluginName} を使用できません", - "xpack.server.checkLicense.errorUnavailableMessage": "現在ライセンス情報が利用できないため {pluginName} を使用できません。", - "xpack.server.checkLicense.errorUnsupportedMessage": "ご使用の {licenseType} ライセンスは {pluginName} をサポートしていません。ライセンスをアップグレードしてください。", "xpack.snapshotRestore.addPolicy.breadcrumbTitle": "ポリシーを追加", "xpack.snapshotRestore.addPolicy.loadingIndicesDescription": "利用可能なインデックスを読み込み中…", "xpack.snapshotRestore.addPolicy.LoadingIndicesErrorMessage": "利用可能なインデックスを読み込み中にエラーが発生", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index d93c8054268e7..103ff4ab146a4 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -801,8 +801,6 @@ "data.indexPatterns.ensureDefaultIndexPattern.bannerLabel": "若要在 Kibana 中可视化和浏览数据,您需要创建索引模式,以从 Elasticsearch 检索数据。", "data.indexPatterns.fetchFieldErrorTitle": "提取索引模式 {title} (ID: {id}) 的字段时出错", "data.indexPatterns.unableWriteLabel": "无法写入索引模式!请刷新页面以获取此索引模式的最新更改。", - "data.indexPatterns.unknownFieldErrorMessage": "indexPattern “{title}” 中的字段 “{name}” 使用未知字段类型。", - "data.indexPatterns.unknownFieldHeader": "未知字段类型 {type}", "data.noDataPopover.content": "此时间范围不包含任何数据。增大或调整时间范围,以查看更多的字段并创建图表。", "data.noDataPopover.dismissAction": "不再显示", "data.noDataPopover.subtitle": "提示", @@ -4809,8 +4807,6 @@ "xpack.apm.serviceMap.avgMemoryUsagePopoverStat": "内存使用率(平均值)", "xpack.apm.serviceMap.avgReqPerMinutePopoverMetric": "每分钟请求数(平均)", "xpack.apm.serviceMap.avgTransDurationPopoverStat": "事务持续时间(平均值)", - "xpack.apm.serviceMap.betaBadge": "公测版", - "xpack.apm.serviceMap.betaTooltipMessage": "此功能当前为公测版。如果遇到任何错误或有任何反馈,请报告问题或访问我们的论坛。", "xpack.apm.serviceMap.center": "中", "xpack.apm.serviceMap.download": "下载", "xpack.apm.serviceMap.emptyBanner.docsLink": "在文档中了解详情", @@ -8823,7 +8819,6 @@ "xpack.infra.registerFeatures.logsDescription": "实时流式传输日志或在类似控制台的工具中滚动浏览历史视图。", "xpack.infra.registerFeatures.logsTitle": "日志", "xpack.infra.sampleDataLinkLabel": "日志", - "xpack.infra.savedView.changeView": "更改视图", "xpack.infra.savedView.currentView": "当前视图", "xpack.infra.savedView.defaultViewNameHosts": "默认视图", "xpack.infra.savedView.errorOnCreate.duplicateViewName": "具有该名称的视图已存在。", @@ -9297,15 +9292,7 @@ "xpack.ingestManager.setupPage.missingRequirementsKibanaTitle": "在您的 Kibana 配置中,启用:", "xpack.ingestManager.setupPage.tlsFlagText": "{kibanaSecurityLink}。将 {securityFlag} 设置为 {true}。出于开发目的,作为非安全的备用方案可以通过将 {tlsFlag} 设置为 {true} 来禁用 {tlsLink}。", "xpack.ingestManager.setupPage.tlsLink": "TLS", - "xpack.ingestManager.unenrollAgents.confirmModal.cancelButtonLabel": "取消", - "xpack.ingestManager.unenrollAgents.confirmModal.confirmButtonLabel": "取消注册", - "xpack.ingestManager.unenrollAgents.confirmModal.deleteMultipleTitle": "取消注册 {count, plural, one {# 个代理} other {# 个代理}}?", - "xpack.ingestManager.unenrollAgents.confirmModal.deleteSingleTitle": "取消注册“{id}”?", - "xpack.ingestManager.unenrollAgents.confirmModal.forceDeleteSingleTitle": "强制取消注册代理“{id}”?", - "xpack.ingestManager.unenrollAgents.confirmModal.loadingButtonLabel": "正在加载……", - "xpack.ingestManager.unenrollAgents.fatalErrorNotificationTitle": "取消注册代理时出错", - "xpack.ingestManager.unenrollAgents.successForceSingleNotificationTitle": "代理“{id}”已取消注册", - "xpack.ingestManager.unenrollAgents.successSingleNotificationTitle": "取消注册代理“{id}”", + "xpack.ingestManager.unenrollAgents.cancelButtonLabel": "取消", "xpack.ingestPipelines.app.checkingPrivilegesDescription": "正在检查权限……", "xpack.ingestPipelines.app.checkingPrivilegesErrorMessage": "从服务器获取用户权限时出错。", "xpack.ingestPipelines.app.deniedPrivilegeDescription": "要使用“采集管道”,必须具有{privilegesCount, plural, one {以下集群权限} other {以下集群权限}}:{missingPrivileges}。", @@ -10535,8 +10522,6 @@ "xpack.ml.calendarsList.deleteCalendars.deletingCalendarSuccessNotificationMessage": "已删除 {messageId}", "xpack.ml.calendarsList.deleteCalendarsModal.cancelButtonLabel": "取消", "xpack.ml.calendarsList.deleteCalendarsModal.deleteButtonLabel": "删除", - "xpack.ml.calendarsList.deleteCalendarsModal.deleteCalendarsDescription": "是否删除{calendarsCount, plural, one {此日历} other {这些日历}}?{calendarsList}", - "xpack.ml.calendarsList.deleteCalendarsModal.deleteCalendarTitle": "删除日历", "xpack.ml.calendarsList.errorWithLoadingListOfCalendarsErrorMessage": "加载日历列表时出错。", "xpack.ml.calendarsList.table.allJobsLabel": "应用到所有作业", "xpack.ml.calendarsList.table.deleteButtonLabel": "删除", @@ -12598,7 +12583,6 @@ "xpack.monitoring.cluster.overview.esPanel.versionLabel": "版本", "xpack.monitoring.cluster.overview.esPanel.versionNotAvailableDescription": "不适用", "xpack.monitoring.cluster.overview.esPanel.warnLogsTooltipText": "警告日志数", - "xpack.monitoring.cluster.overview.healthStatusDescription": "运行状况为{status}", "xpack.monitoring.cluster.overview.kibanaPanel.connectionsLabel": "连接", "xpack.monitoring.cluster.overview.kibanaPanel.instancesCountLinkAriaLabel": "Kibana 实例:{instancesCount}", "xpack.monitoring.cluster.overview.kibanaPanel.instancesCountLinkLabel": "实例:{instancesCount}", @@ -12717,8 +12701,6 @@ "xpack.monitoring.elasticsearch.nodeDetailStatus.transportAddress": "传输地址", "xpack.monitoring.elasticsearch.nodeDetailStatus.typeLabel": "类型", "xpack.monitoring.elasticsearch.nodes.alertsColumnTitle": "告警", - "xpack.monitoring.elasticsearch.nodes.cells.maxText": "{metric} 最大值", - "xpack.monitoring.elasticsearch.nodes.cells.minText": "{metric} 最小值", "xpack.monitoring.elasticsearch.nodes.cpuThrottlingColumnTitle": "CPU 限制", "xpack.monitoring.elasticsearch.nodes.cpuUsageColumnTitle": "CPU 使用", "xpack.monitoring.elasticsearch.nodes.diskFreeSpaceColumnTitle": "磁盘可用空间", @@ -14899,7 +14881,7 @@ "xpack.securitySolution.auditd.violatedSeLinuxPolicyDescription": "已违反 selinux 策略", "xpack.securitySolution.auditd.wasAuthorizedToUseDescription": "有权使用", "xpack.securitySolution.auditd.withResultDescription": ",结果为", - "xpack.securitySolution.authenticationsTable.authenticationFailures": "身份验证", + "xpack.securitySolution.authenticationsTable.authentications": "身份验证", "xpack.securitySolution.authenticationsTable.failures": "错误", "xpack.securitySolution.authenticationsTable.lastFailedDestination": "上一失败目标", "xpack.securitySolution.authenticationsTable.lastFailedSource": "上一失败源", @@ -15903,19 +15885,14 @@ "xpack.securitySolution.endpoint.resolver.panel.error.goBack": "单击此链接以返回到所有进程的列表。", "xpack.securitySolution.endpoint.resolver.panel.processDescList.events": "事件", "xpack.securitySolution.endpoint.resolver.panel.processEventCounts.events": "事件", - "xpack.securitySolution.endpoint.resolver.panel.processEventListByType.eventDescriptiveName": "{descriptor} {subject}", "xpack.securitySolution.endpoint.resolver.panel.processEventListByType.events": "事件", "xpack.securitySolution.endpoint.resolver.panel.relatedCounts.numberOfEventsInCrumb": "{totalCount} 个事件", - "xpack.securitySolution.endpoint.resolver.panel.relatedDetail.missing": "找不到相关事件。", "xpack.securitySolution.endpoint.resolver.panel.relatedDetail.wait": "等候事件......", "xpack.securitySolution.endpoint.resolver.panel.relatedEventDetail.atTime": "@ {date}", "xpack.securitySolution.endpoint.resolver.panel.relatedEventDetail.categoryAndType": "{category} {eventType}", "xpack.securitySolution.endpoint.resolver.panel.relatedEventDetail.countByCategory": "{count} 个{category}", "xpack.securitySolution.endpoint.resolver.panel.relatedEventDetail.detailsForProcessName": "{processName} 的详情", - "xpack.securitySolution.endpoint.resolver.panel.relatedEventDetail.eventDescriptiveName": "{descriptor} {subject}", - "xpack.securitySolution.endpoint.resolver.panel.relatedEventDetail.eventDescriptiveNameInTitle": "{descriptor} {subject}", "xpack.securitySolution.endpoint.resolver.panel.relatedEventDetail.events": "事件", - "xpack.securitySolution.endpoint.resolver.panel.relatedEventDetail.NA": "不可用", "xpack.securitySolution.endpoint.resolver.panel.relatedEventDetail.numberOfEvents": "{totalCount} 个事件", "xpack.securitySolution.endpoint.resolver.panel.relatedEventList.countByCategory": "{count} 个{category}", "xpack.securitySolution.endpoint.resolver.panel.relatedEventList.numberOfEvents": "{totalCount} 个事件", @@ -16698,9 +16675,6 @@ "xpack.securitySolution.zeek.sfDescription": "正常 SYN/FIN 完成", "xpack.securitySolution.zeek.shDescription": "发起方已发送 SYN,后跟 FIN,响应方未发送 SYN ACK", "xpack.securitySolution.zeek.shrDescription": "响应方已发送 SYN ACK,后跟 FIN,发起方未发送 SYN", - "xpack.server.checkLicense.errorExpiredMessage": "您不能使用 {pluginName},因为您的{licenseType}许可证已过期", - "xpack.server.checkLicense.errorUnavailableMessage": "您不能使用 {pluginName},因为许可证信息当前不可用。", - "xpack.server.checkLicense.errorUnsupportedMessage": "您的{licenseType}许可证不支持 {pluginName}。请升级您的许可证。", "xpack.snapshotRestore.addPolicy.breadcrumbTitle": "添加策略", "xpack.snapshotRestore.addPolicy.loadingIndicesDescription": "正在加载可用索引……", "xpack.snapshotRestore.addPolicy.LoadingIndicesErrorMessage": "加载可用索引时出错", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx index 0742ed8a778ef..2bcd87830901b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx @@ -61,6 +61,7 @@ export const AddMessageVariables: React.FunctionComponent = ({ setIsVariablesPopoverOpen(true)} iconType="indexOpen" diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.tsx index 495707db4975c..0a04db1b5ddfa 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.tsx @@ -32,48 +32,47 @@ export const IndexParamsFields = ({ }; return ( - <> - 0 ? ((documents[0] as unknown) as string) : undefined + 0 ? ((documents[0] as unknown) as string) : undefined + } + label={i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.documentsFieldLabel', + { + defaultMessage: 'Document to index', } - label={i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.documentsFieldLabel', - { - defaultMessage: 'Document to index', - } - )} - aria-label={i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.jsonDocAriaLabel', - { - defaultMessage: 'Code editor', - } - )} - errors={errors.documents as string[]} - onDocumentsChange={onDocumentsChange} - helpText={ - - - + )} + aria-label={i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.jsonDocAriaLabel', + { + defaultMessage: 'Code editor', } - onBlur={() => { - if ( - !(documents && documents.length > 0 ? ((documents[0] as unknown) as string) : undefined) - ) { - // set document as empty to turn on the validation for non empty valid JSON object - onDocumentsChange('{}'); - } - }} - /> - + )} + errors={errors.documents as string[]} + onDocumentsChange={onDocumentsChange} + helpText={ + + + + } + onBlur={() => { + if ( + !(documents && documents.length > 0 ? ((documents[0] as unknown) as string) : undefined) + ) { + // set document as empty to turn on the validation for non empty valid JSON object + onDocumentsChange('{}'); + } + }} + /> ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/delete_modal_confirmation.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/delete_modal_confirmation.tsx index 5862a567f71ba..a093b9c511970 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/delete_modal_confirmation.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/delete_modal_confirmation.tsx @@ -5,7 +5,7 @@ */ import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { HttpSetup } from 'kibana/public'; import { useAppDependencies } from '../app_context'; @@ -17,6 +17,7 @@ export const DeleteModalConfirmation = ({ onErrors, singleTitle, multipleTitle, + setIsLoadingState, }: { idsToDelete: string[]; apiDeleteCall: ({ @@ -31,10 +32,17 @@ export const DeleteModalConfirmation = ({ onErrors: () => void; singleTitle: string; multipleTitle: string; + setIsLoadingState: (isLoading: boolean) => void; }) => { + const [deleteModalFlyoutVisible, setDeleteModalVisibility] = useState(false); + + useEffect(() => { + setDeleteModalVisibility(idsToDelete.length > 0); + }, [idsToDelete]); + const { http, toastNotifications } = useAppDependencies(); const numIdsToDelete = idsToDelete.length; - if (!numIdsToDelete) { + if (!deleteModalFlyoutVisible) { return null; } const confirmModalText = i18n.translate( @@ -65,12 +73,18 @@ export const DeleteModalConfirmation = ({ buttonColor="danger" data-test-subj="deleteIdsConfirmation" title={confirmButtonText} - onCancel={() => onCancel()} + onCancel={() => { + setDeleteModalVisibility(false); + onCancel(); + }} onConfirm={async () => { + setDeleteModalVisibility(false); + setIsLoadingState(true); const { successes, errors } = await apiDeleteCall({ ids: idsToDelete, http }); + setIsLoadingState(false); + const numSuccesses = successes.length; const numErrors = errors.length; - onDeleted(successes); if (numSuccesses > 0) { toastNotifications.addSuccess( i18n.translate( @@ -95,8 +109,9 @@ export const DeleteModalConfirmation = ({ } ) ); - onErrors(); + await onErrors(); } + await onDeleted(successes); }} cancelButtonText={cancelButtonText} confirmButtonText={confirmButtonText} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts index 43b22361aea36..ad3a5b40bd00d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts @@ -12,6 +12,7 @@ import { loadActionTypes, loadAllActions, updateActionConnector, + executeAction, } from './action_connector_api'; const http = httpServiceMock.createStartContract(); @@ -128,3 +129,32 @@ describe('deleteActions', () => { `); }); }); + +describe('executeAction', () => { + test('should call execute API', async () => { + const id = '123'; + const params = { + stringParams: 'someString', + numericParams: 123, + }; + + http.post.mockResolvedValueOnce({ + actionId: id, + status: 'ok', + }); + + const result = await executeAction({ id, http, params }); + expect(result).toEqual({ + actionId: id, + status: 'ok', + }); + expect(http.post.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "/api/actions/action/123/_execute", + Object { + "body": "{\\"params\\":{\\"stringParams\\":\\"someString\\",\\"numericParams\\":123}}", + }, + ] + `); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.ts index 46a676ac06539..c2c7139d13bf0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.ts @@ -7,6 +7,7 @@ import { HttpSetup } from 'kibana/public'; import { BASE_ACTION_API_PATH } from '../constants'; import { ActionConnector, ActionConnectorWithoutId, ActionType } from '../../types'; +import { ActionTypeExecutorResult } from '../../../../../plugins/actions/common'; export async function loadActionTypes({ http }: { http: HttpSetup }): Promise { return await http.get(`${BASE_ACTION_API_PATH}/list_action_types`); @@ -65,3 +66,17 @@ export async function deleteActions({ ); return { successes, errors }; } + +export async function executeAction({ + id, + params, + http, +}: { + id: string; + http: HttpSetup; + params: Record; +}): Promise> { + return await http.post(`${BASE_ACTION_API_PATH}/action/${id}/_execute`, { + body: JSON.stringify({ params }), + }); +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.scss b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.scss new file mode 100644 index 0000000000000..873a3ceb762cd --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.scss @@ -0,0 +1,3 @@ +.connectorEditFlyoutTabs { + margin-bottom: '-25px'; +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx index dd9eeae266987..0c2f4df0ca52b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx @@ -152,6 +152,6 @@ describe('connector_edit_flyout', () => { const preconfiguredBadge = wrapper.find('[data-test-subj="preconfiguredBadge"]'); expect(preconfiguredBadge.exists()).toBeTruthy(); - expect(wrapper.find('[data-test-subj="saveEditedActionButton"]').exists()).toBeFalsy(); + expect(wrapper.find('[data-test-subj="saveAndCloseEditedActionButton"]').exists()).toBeFalsy(); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx index ca75e730062ab..fc902a4fabcd8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx @@ -19,15 +19,21 @@ import { EuiBetaBadge, EuiText, EuiLink, + EuiTabs, + EuiTab, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { Option, none, some } from 'fp-ts/lib/Option'; import { ActionConnectorForm, validateBaseProperties } from './action_connector_form'; +import { TestConnectorForm } from './test_connector_form'; import { ActionConnectorTableItem, ActionConnector, IErrorObject } from '../../../types'; import { connectorReducer } from './connector_reducer'; -import { updateActionConnector } from '../../lib/action_connector_api'; +import { updateActionConnector, executeAction } from '../../lib/action_connector_api'; import { hasSaveActionsCapability } from '../../lib/capabilities'; import { useActionsConnectorsContext } from '../../context/actions_connectors_context'; import { PLUGIN } from '../../constants/plugin'; +import { ActionTypeExecutorResult } from '../../../../../actions/common'; +import './connector_edit_flyout.scss'; export interface ConnectorEditProps { initialConnector: ActionConnectorTableItem; @@ -40,7 +46,6 @@ export const ConnectorEditFlyout = ({ editFlyoutVisible, setEditFlyoutVisibility, }: ConnectorEditProps) => { - let hasErrors = false; const { http, toastNotifications, @@ -56,13 +61,26 @@ export const ConnectorEditFlyout = ({ connector: { ...initialConnector, secrets: {} }, }); const [isSaving, setIsSaving] = useState(false); + const [selectedTab, setTab] = useState<'config' | 'test'>('config'); + + const [hasChanges, setHasChanges] = useState(false); const setConnector = (key: string, value: any) => { dispatch({ command: { type: 'setConnector' }, payload: { key, value } }); }; + const [testExecutionActionParams, setTestExecutionActionParams] = useState< + Record + >({}); + const [testExecutionResult, setTestExecutionResult] = useState< + Option> + >(none); + const [isExecutingAction, setIsExecutinAction] = useState(false); + const closeFlyout = useCallback(() => { setEditFlyoutVisibility(false); setConnector('connector', { ...initialConnector, secrets: {} }); + setHasChanges(false); + setTestExecutionResult(none); // eslint-disable-next-line react-hooks/exhaustive-deps }, [setEditFlyoutVisibility]); @@ -71,11 +89,13 @@ export const ConnectorEditFlyout = ({ } const actionTypeModel = actionTypeRegistry.get(connector.actionTypeId); - const errors = { + const errorsInConnectorConfig = { ...actionTypeModel?.validateConnector(connector).errors, ...validateBaseProperties(connector).errors, } as IErrorObject; - hasErrors = !!Object.keys(errors).find((errorKey) => errors[errorKey].length >= 1); + const hasErrorsInConnectorConfig = !!Object.keys(errorsInConnectorConfig).find( + (errorKey) => errorsInConnectorConfig[errorKey].length >= 1 + ); const onActionConnectorSave = async (): Promise => await updateActionConnector({ http, connector, id: connector.id }) @@ -173,6 +193,32 @@ export const ConnectorEditFlyout = ({
); + const onExecutAction = () => { + setIsExecutinAction(true); + return executeAction({ id: connector.id, params: testExecutionActionParams, http }).then( + (result) => { + setIsExecutinAction(false); + setTestExecutionResult(some(result)); + return result; + } + ); + }; + + const onSaveClicked = async (closeAfterSave: boolean = true) => { + setIsSaving(true); + const savedAction = await onActionConnectorSave(); + setIsSaving(false); + if (savedAction) { + setHasChanges(false); + if (closeAfterSave) { + closeFlyout(); + } + if (reloadConnectors) { + reloadConnectors(); + } + } + }; + return ( @@ -184,40 +230,78 @@ export const ConnectorEditFlyout = ({ ) : null} {flyoutTitle} + + setTab('config')} + data-test-subj="configureConnectorTab" + isSelected={'config' === selectedTab} + > + {i18n.translate('xpack.triggersActionsUI.sections.editConnectorForm.tabText', { + defaultMessage: 'Configuration', + })} + + setTab('test')} + data-test-subj="testConnectorTab" + isSelected={'test' === selectedTab} + > + {i18n.translate('xpack.triggersActionsUI.sections.testConnectorForm.tabText', { + defaultMessage: 'Test', + })} + + - {!connector.isPreconfigured ? ( - { + setHasChanges(true); + // if the user changes the connector, "forget" the last execution + // so the user comes back to a clean form ready to run a fresh test + setTestExecutionResult(none); + dispatch(changes); + }} + actionTypeRegistry={actionTypeRegistry} + http={http} + docLinks={docLinks} + capabilities={capabilities} + consumer={consumer} + /> + ) : ( + + + {i18n.translate( + 'xpack.triggersActionsUI.sections.editConnectorForm.descriptionText', + { + defaultMessage: 'This connector is readonly.', + } + )} + + + + + + ) + ) : ( + - ) : ( - - - {i18n.translate( - 'xpack.triggersActionsUI.sections.editConnectorForm.descriptionText', - { - defaultMessage: 'This connector is readonly.', - } - )} - - - - - )} @@ -232,35 +316,48 @@ export const ConnectorEditFlyout = ({ )}
- {canSave && actionTypeModel && !connector.isPreconfigured ? ( - - { - setIsSaving(true); - const savedAction = await onActionConnectorSave(); - setIsSaving(false); - if (savedAction) { - closeFlyout(); - if (reloadConnectors) { - reloadConnectors(); - } - } - }} - > - - - - ) : null} + + + {canSave && actionTypeModel && !connector.isPreconfigured ? ( + + + { + await onSaveClicked(false); + }} + > + + + + + { + await onSaveClicked(); + }} + > + + + + + ) : null} + + diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.test.tsx new file mode 100644 index 0000000000000..482bccb5517f1 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.test.tsx @@ -0,0 +1,212 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { lazy } from 'react'; +import { I18nProvider } from '@kbn/i18n/react'; +import { coreMock } from '../../../../../../../src/core/public/mocks'; +import TestConnectorForm from './test_connector_form'; +import { none, some } from 'fp-ts/lib/Option'; +import { ActionConnector, ValidationResult } from '../../../types'; +import { actionTypeRegistryMock } from '../../action_type_registry.mock'; +import { ActionsConnectorsContextProvider } from '../../context/actions_connectors_context'; +import { EuiFormRow, EuiFieldText, EuiText, EuiLink, EuiForm, EuiSelect } from '@elastic/eui'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; + +const mockedActionParamsFields = lazy(async () => ({ + default() { + return ( + + + + + + + Link to some help + + } + > + + + + ); + }, +})); + +const actionType = { + id: 'my-action-type', + iconClass: 'test', + selectMessage: 'test', + validateConnector: (): ValidationResult => { + return { errors: {} }; + }, + validateParams: (): ValidationResult => { + const validationResult = { errors: {} }; + return validationResult; + }, + actionConnectorFields: null, + actionParamsFields: mockedActionParamsFields, +}; + +describe('test_connector_form', () => { + let deps: any; + let actionTypeRegistry; + beforeAll(async () => { + actionTypeRegistry = actionTypeRegistryMock.create(); + + const mocks = coreMock.createSetup(); + const [ + { + application: { capabilities }, + }, + ] = await mocks.getStartServices(); + deps = { + http: mocks.http, + toastNotifications: mocks.notifications.toasts, + docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' }, + actionTypeRegistry, + capabilities, + }; + actionTypeRegistry.get.mockReturnValue(actionType); + }); + + it('renders initially as the action form and execute button and no result', async () => { + const connector = { + actionTypeId: actionType.id, + config: {}, + secrets: {}, + } as ActionConnector; + const wrapper = mountWithIntl( + + { + return new Promise(() => {}); + }, + docLinks: deps!.docLinks, + }} + > + {}} + isExecutingAction={false} + onExecutAction={async () => ({ + actionId: '', + status: 'ok', + })} + executionResult={none} + /> + + + ); + const executeActionButton = wrapper?.find('[data-test-subj="executeActionButton"]'); + expect(executeActionButton?.exists()).toBeTruthy(); + expect(executeActionButton?.first().prop('isDisabled')).toBe(false); + + const result = wrapper?.find('[data-test-subj="executionAwaiting"]'); + expect(result?.exists()).toBeTruthy(); + }); + + it('renders successful results', async () => { + const connector = { + actionTypeId: actionType.id, + config: {}, + secrets: {}, + } as ActionConnector; + const wrapper = mountWithIntl( + + { + return new Promise(() => {}); + }, + docLinks: deps!.docLinks, + }} + > + {}} + isExecutingAction={false} + onExecutAction={async () => ({ + actionId: '', + status: 'ok', + })} + executionResult={some({ + actionId: '', + status: 'ok', + })} + /> + + + ); + const result = wrapper?.find('[data-test-subj="executionSuccessfulResult"]'); + expect(result?.exists()).toBeTruthy(); + }); + + it('renders failure results', async () => { + const connector = { + actionTypeId: actionType.id, + config: {}, + secrets: {}, + } as ActionConnector; + const wrapper = mountWithIntl( + + { + return new Promise(() => {}); + }, + docLinks: deps!.docLinks, + }} + > + {}} + isExecutingAction={false} + onExecutAction={async () => ({ + actionId: '', + status: 'error', + message: 'Error Message', + })} + executionResult={some({ + actionId: '', + status: 'error', + message: 'Error Message', + })} + /> + + + ); + const result = wrapper?.find('[data-test-subj="executionFailureResult"]'); + expect(result?.exists()).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.tsx new file mode 100644 index 0000000000000..a73fd4e22e637 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.tsx @@ -0,0 +1,224 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { Fragment, Suspense } from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiButton, + EuiSteps, + EuiLoadingSpinner, + EuiDescriptionList, + EuiCallOut, + EuiSpacer, +} from '@elastic/eui'; +import { Option, map, getOrElse } from 'fp-ts/lib/Option'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { ActionConnector } from '../../../types'; +import { useActionsConnectorsContext } from '../../context/actions_connectors_context'; +import { ActionTypeExecutorResult } from '../../../../../actions/common'; + +export interface ConnectorAddFlyoutProps { + connector: ActionConnector; + executeEnabled: boolean; + isExecutingAction: boolean; + setActionParams: (params: Record) => void; + actionParams: Record; + onExecutAction: () => Promise>; + executionResult: Option>; +} + +export const TestConnectorForm = ({ + connector, + executeEnabled, + executionResult, + actionParams, + setActionParams, + onExecutAction, + isExecutingAction, +}: ConnectorAddFlyoutProps) => { + const { actionTypeRegistry, docLinks } = useActionsConnectorsContext(); + const actionTypeModel = actionTypeRegistry.get(connector.actionTypeId); + const ParamsFieldsComponent = actionTypeModel.actionParamsFields; + + const actionErrors = actionTypeModel?.validateParams(actionParams); + const hasErrors = !!Object.values(actionErrors.errors).find((errors) => errors.length > 0); + + const steps = [ + { + title: 'Create an action', + children: ParamsFieldsComponent ? ( + + + + + + } + > + + setActionParams({ + ...actionParams, + [field]: value, + }) + } + messageVariables={[]} + docLinks={docLinks} + actionConnector={connector} + /> + + ) : ( + +

This Connector does not require any Action Parameter.

+
+ ), + }, + { + title: 'Run the action', + children: ( + + {executeEnabled ? null : ( + + +

+ +

+
+ +
+ )} + + + + + +
+ ), + }, + { + title: 'Results', + children: pipe( + executionResult, + map((result) => + result.status === 'ok' ? ( + + ) : ( + + ) + ), + getOrElse(() => ) + ), + }, + ]; + + return ; +}; + +const AwaitingExecution = () => ( + +

+ +

+
+); + +const SuccessfulExecution = () => ( + +

+ +

+
+); + +const FailedExecussion = ({ + executionResult: { message, serviceMessage }, +}: { + executionResult: ActionTypeExecutorResult; +}) => { + const items = [ + { + title: i18n.translate( + 'xpack.triggersActionsUI.sections.testConnectorForm.executionFailureDescription', + { + defaultMessage: 'The following error was found:', + } + ), + description: + message ?? + i18n.translate( + 'xpack.triggersActionsUI.sections.testConnectorForm.executionFailureUnknownReason', + { + defaultMessage: 'Unknown reason', + } + ), + }, + ]; + if (serviceMessage) { + items.push({ + title: i18n.translate( + 'xpack.triggersActionsUI.sections.testConnectorForm.executionFailureAdditionalDetails', + { + defaultMessage: 'Details:', + } + ), + description: serviceMessage, + }); + } + return ( + + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export { TestConnectorForm as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx index 837529bfc938d..6bc9fd8e7e5a8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx @@ -194,55 +194,15 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { truncateText: true, }, { - field: 'isPreconfigured', name: '', - render: (value: number, item: ActionConnectorTableItem) => { - if (item.isPreconfigured) { - return ( - - - - - - ); - } + render: (item: ActionConnectorTableItem) => { return ( - - - setConnectorsToDelete([item.id])} - iconType={'trash'} - /> - - + setConnectorsToDelete([item.id])} + /> ); }, @@ -344,28 +304,6 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { /> ); - const noPermissionPrompt = ( - - - - } - body={ -

- -

- } - /> - ); - return (
{ 'xpack.triggersActionsUI.sections.actionsConnectorsList.multipleTitle', { defaultMessage: 'connectors' } )} + setIsLoadingState={(isLoading: boolean) => setIsLoadingActionTypes(isLoading)} /> {/* Render the view based on if there's data or if they can save */} @@ -411,7 +350,7 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { {data.length === 0 && canSave && !isLoadingActions && !isLoadingActionTypes && ( setAddFlyoutVisibility(true)} /> )} - {data.length === 0 && !canSave && noPermissionPrompt} + {data.length === 0 && !canSave && } { function getActionsCountByActionType(actions: ActionConnector[], actionTypeId: string) { return actions.filter((action) => action.actionTypeId === actionTypeId).length; } + +const DeleteOperation: React.FunctionComponent<{ + item: ActionConnectorTableItem; + canDelete: boolean; + onDelete: () => void; +}> = ({ item, canDelete, onDelete }) => { + if (item.isPreconfigured) { + return ( + + + + ); + } + return ( + + + + + + ); +}; + +const NoPermissionPrompt: React.FunctionComponent<{}> = () => ( + + + + } + body={ +

+ +

+ } + /> +); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx index 3d55c51e45281..7d0354589ecb8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx @@ -404,16 +404,16 @@ export const AlertsList: React.FunctionComponent = () => {
{ - loadAlertsData(); - setSelectedIds([]); setAlertsToDelete([]); + setSelectedIds([]); + await loadAlertsData(); }} onErrors={async () => { // Refresh the alerts from the server, some alerts may have beend deleted await loadAlertsData(); setAlertsToDelete([]); }} - onCancel={async () => { + onCancel={() => { setAlertsToDelete([]); }} apiDeleteCall={deleteAlerts} @@ -424,6 +424,9 @@ export const AlertsList: React.FunctionComponent = () => { multipleTitle={i18n.translate('xpack.triggersActionsUI.sections.alertsList.multipleTitle', { defaultMessage: 'alerts', })} + setIsLoadingState={(isLoading: boolean) => { + setAlertsState({ ...alertsState, isLoading }); + }} /> {loadedItems.length || isFilterApplied ? ( diff --git a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.test.ts b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.test.ts index 83232bbce1ba7..cdd357f3560b8 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.test.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.test.ts @@ -437,8 +437,7 @@ describe('DynamicActionManager', () => { name: 'foo', config: {}, }; - - await expect(manager.createEvent(action, ['SELECT_RANGE_TRIGGER'])).rejects; + await expect(manager.createEvent(action, ['SELECT_RANGE_TRIGGER'])).rejects.toThrow(); }); }); }); @@ -704,4 +703,18 @@ describe('DynamicActionManager', () => { expect(basicAndGoldActions).toHaveLength(2); }); + + test("failing to revive/kill an action doesn't fail action manager", async () => { + const { manager, uiActions, storage } = setup([event1, event3, event2]); + + uiActions.registerActionFactory(actionFactoryDefinition1); + + await manager.start(); + + expect(uiActions.getTriggerActions('VALUE_CLICK_TRIGGER')).toHaveLength(2); + expect(await storage.list()).toEqual([event1, event3, event2]); + + await manager.stop(); + expect(uiActions.getTriggerActions('VALUE_CLICK_TRIGGER')).toHaveLength(0); + }); }); diff --git a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.ts b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.ts index 471b929fdbc06..b414296690c9e 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.ts @@ -34,7 +34,13 @@ export interface DynamicActionManagerParams { storage: ActionStorage; uiActions: Pick< StartContract, - 'registerAction' | 'attachAction' | 'unregisterAction' | 'detachAction' | 'getActionFactory' + | 'registerAction' + | 'attachAction' + | 'unregisterAction' + | 'detachAction' + | 'hasAction' + | 'getActionFactory' + | 'hasActionFactory' >; isCompatible: (context: C) => Promise; } @@ -73,8 +79,17 @@ export class DynamicActionManager { const actionId = this.generateActionId(eventId); + if (!uiActions.hasActionFactory(action.factoryId)) { + // eslint-disable-next-line no-console + console.warn( + `Action factory for action [action.factoryId = ${action.factoryId}] doesn't exist. Skipping action [action.name = ${action.name}] revive.` + ); + return; + } + const factory = uiActions.getActionFactory(event.action.factoryId); const actionDefinition: ActionDefinition = factory.create(action as SerializedAction); + uiActions.registerAction({ ...actionDefinition, id: actionId, @@ -100,6 +115,7 @@ export class DynamicActionManager { protected killAction({ eventId, triggers }: SerializedEvent) { const { uiActions } = this.params; const actionId = this.generateActionId(eventId); + if (!uiActions.hasAction(actionId)) return; for (const trigger of triggers) uiActions.detachAction(trigger as any, actionId); uiActions.unregisterAction(actionId); @@ -157,6 +173,7 @@ export class DynamicActionManager { try { const events = await this.params.storage.list(); for (const event of events) this.reviveAction(event); + this.ui.transitions.finishFetching(events); } catch (error) { this.ui.transitions.failFetching(error instanceof Error ? error : { message: String(error) }); diff --git a/x-pack/plugins/ui_actions_enhanced/public/mocks.ts b/x-pack/plugins/ui_actions_enhanced/public/mocks.ts index 9eb0a06b6dbaf..1900f04b0c7d8 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/mocks.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/mocks.ts @@ -29,6 +29,7 @@ const createStartContract = (): Start => { ...uiActionsPluginMock.createStartContract(), getActionFactories: jest.fn(), getActionFactory: jest.fn(), + hasActionFactory: jest.fn(), FlyoutManageDrilldowns: jest.fn(), telemetry: jest.fn(), extract: jest.fn(), diff --git a/x-pack/plugins/ui_actions_enhanced/public/plugin.ts b/x-pack/plugins/ui_actions_enhanced/public/plugin.ts index b05c08c4c77d0..31236d2ea9779 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/plugin.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/plugin.ts @@ -61,7 +61,12 @@ export interface StartContract extends UiActionsStart, Pick< UiActionsServiceEnhancements, - 'getActionFactory' | 'getActionFactories' | 'telemetry' | 'extract' | 'inject' + | 'getActionFactory' + | 'hasActionFactory' + | 'getActionFactories' + | 'telemetry' + | 'extract' + | 'inject' > { FlyoutManageDrilldowns: ReturnType; } diff --git a/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts b/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts index 5e40d803962de..cbbd88e65e841 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts @@ -79,6 +79,10 @@ export class UiActionsServiceEnhancements return actionFactory; }; + public readonly hasActionFactory = (actionFactoryId: string): boolean => { + return this.actionFactories.has(actionFactoryId); + }; + /** * Returns an array of all action factories. */ diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs.tsx index 146cebabbb382..110eff36e3df9 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs.tsx @@ -239,7 +239,7 @@ export class UpgradeAssistantTabs extends React.Component { this.setState({ telemetryState: TelemetryState.Running }); - await this.props.http.fetch('/api/upgrade_assistant/telemetry/ui_open', { + await this.props.http.fetch('/api/upgrade_assistant/stats/ui_open', { method: 'PUT', body: JSON.stringify(set({}, tabName, true)), }); diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/button.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/button.tsx index a20f4117f693d..747430f455f22 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/button.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/button.tsx @@ -239,7 +239,7 @@ export class ReindexButton extends React.Component { }); afterEach(() => jest.clearAllMocks()); - describe('PUT /api/upgrade_assistant/telemetry/ui_open', () => { + describe('PUT /api/upgrade_assistant/stats/ui_open', () => { it('returns correct payload with single option', async () => { const returnPayload = { overview: true, @@ -51,7 +51,7 @@ describe('Upgrade Assistant Telemetry API', () => { const resp = await routeDependencies.router.getHandler({ method: 'put', - pathPattern: '/api/upgrade_assistant/telemetry/ui_open', + pathPattern: '/api/upgrade_assistant/stats/ui_open', })( routeHandlerContextMock, createRequestMock({ body: returnPayload }), @@ -72,7 +72,7 @@ describe('Upgrade Assistant Telemetry API', () => { const resp = await routeDependencies.router.getHandler({ method: 'put', - pathPattern: '/api/upgrade_assistant/telemetry/ui_open', + pathPattern: '/api/upgrade_assistant/stats/ui_open', })( routeHandlerContextMock, createRequestMock({ @@ -93,7 +93,7 @@ describe('Upgrade Assistant Telemetry API', () => { const resp = await routeDependencies.router.getHandler({ method: 'put', - pathPattern: '/api/upgrade_assistant/telemetry/ui_open', + pathPattern: '/api/upgrade_assistant/stats/ui_open', })( routeHandlerContextMock, createRequestMock({ @@ -108,7 +108,7 @@ describe('Upgrade Assistant Telemetry API', () => { }); }); - describe('PUT /api/upgrade_assistant/telemetry/ui_reindex', () => { + describe('PUT /api/upgrade_assistant/stats/ui_reindex', () => { it('returns correct payload with single option', async () => { const returnPayload = { close: false, @@ -121,7 +121,7 @@ describe('Upgrade Assistant Telemetry API', () => { const resp = await routeDependencies.router.getHandler({ method: 'put', - pathPattern: '/api/upgrade_assistant/telemetry/ui_reindex', + pathPattern: '/api/upgrade_assistant/stats/ui_reindex', })( routeHandlerContextMock, createRequestMock({ @@ -147,7 +147,7 @@ describe('Upgrade Assistant Telemetry API', () => { const resp = await routeDependencies.router.getHandler({ method: 'put', - pathPattern: '/api/upgrade_assistant/telemetry/ui_reindex', + pathPattern: '/api/upgrade_assistant/stats/ui_reindex', })( routeHandlerContextMock, createRequestMock({ @@ -169,7 +169,7 @@ describe('Upgrade Assistant Telemetry API', () => { const resp = await routeDependencies.router.getHandler({ method: 'put', - pathPattern: '/api/upgrade_assistant/telemetry/ui_reindex', + pathPattern: '/api/upgrade_assistant/stats/ui_reindex', })( routeHandlerContextMock, createRequestMock({ diff --git a/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts b/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts index 900a5e64c55c3..71f5de01f6a44 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts @@ -12,7 +12,7 @@ import { RouteDependencies } from '../types'; export function registerTelemetryRoutes({ router, getSavedObjectsService }: RouteDependencies) { router.put( { - path: '/api/upgrade_assistant/telemetry/ui_open', + path: '/api/upgrade_assistant/stats/ui_open', validate: { body: schema.object({ overview: schema.boolean({ defaultValue: false }), @@ -40,7 +40,7 @@ export function registerTelemetryRoutes({ router, getSavedObjectsService }: Rout router.put( { - path: '/api/upgrade_assistant/telemetry/ui_reindex', + path: '/api/upgrade_assistant/stats/ui_reindex', validate: { body: schema.object({ close: schema.boolean({ defaultValue: false }), diff --git a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts index b9dc7945e9579..3fc26811d46eb 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts @@ -8,7 +8,7 @@ import { schema } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; import Mustache from 'mustache'; import { UptimeAlertTypeFactory } from './types'; -import { esKuery, IIndexPattern } from '../../../../../../src/plugins/data/server'; +import { esKuery } from '../../../../../../src/plugins/data/server'; import { JsonObject } from '../../../../../../src/plugins/kibana_utils/common'; import { StatusCheckFilters, @@ -26,7 +26,7 @@ import { UNNAMED_LOCATION } from '../../../common/constants'; import { uptimeAlertWrapper } from './uptime_alert_wrapper'; import { MonitorStatusTranslations } from '../../../common/translations'; import { ESAPICaller } from '../adapters/framework'; -import { getUptimeIndexPattern } from '../requests/get_index_pattern'; +import { getUptimeIndexPattern, IndexPatternTitleAndFields } from '../requests/get_index_pattern'; import { UMServerLibs } from '../lib'; const { MONITOR_STATUS } = ACTION_GROUP_DEFINITIONS; @@ -58,7 +58,7 @@ export const hasFilters = (filters?: StatusCheckFilters) => { }; export const generateFilterDSL = async ( - getIndexPattern: () => Promise, + getIndexPattern: () => Promise, filters: StatusCheckFilters, search: string ): Promise => { diff --git a/x-pack/plugins/uptime/server/lib/requests/get_index_pattern.ts b/x-pack/plugins/uptime/server/lib/requests/get_index_pattern.ts index 345d02b990eb7..1d284143a1ab0 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_index_pattern.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_index_pattern.ts @@ -6,12 +6,17 @@ import { LegacyAPICaller, LegacyCallAPIOptions } from 'src/core/server'; import { UMElasticsearchQueryFn } from '../adapters'; -import { IndexPatternsFetcher, IIndexPattern } from '../../../../../../src/plugins/data/server'; +import { IndexPatternsFetcher, FieldDescriptor } from '../../../../../../src/plugins/data/server'; -export const getUptimeIndexPattern: UMElasticsearchQueryFn<{}, IIndexPattern | undefined> = async ({ - callES, - dynamicSettings, -}) => { +export interface IndexPatternTitleAndFields { + title: string; + fields: FieldDescriptor[]; +} + +export const getUptimeIndexPattern: UMElasticsearchQueryFn< + {}, + IndexPatternTitleAndFields | undefined +> = async ({ callES, dynamicSettings }) => { const callAsCurrentUser: LegacyAPICaller = async ( endpoint: string, clientParams: Record = {}, @@ -28,7 +33,7 @@ export const getUptimeIndexPattern: UMElasticsearchQueryFn<{}, IIndexPattern | u pattern: dynamicSettings.heartbeatIndices, }); - const indexPattern: IIndexPattern = { + const indexPattern: IndexPatternTitleAndFields = { fields, title: dynamicSettings.heartbeatIndices, }; diff --git a/x-pack/plugins/watcher/common/constants/plugin.ts b/x-pack/plugins/watcher/common/constants/plugin.ts index f89ef95e9261f..fa95b86c0673b 100644 --- a/x-pack/plugins/watcher/common/constants/plugin.ts +++ b/x-pack/plugins/watcher/common/constants/plugin.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LICENSE_TYPE_GOLD, LicenseType } from '../../../../legacy/common/constants'; +import { LicenseType } from '../../../licensing/common/types'; export const PLUGIN = { ID: 'watcher', - MINIMUM_LICENSE_REQUIRED: LICENSE_TYPE_GOLD as LicenseType, + MINIMUM_LICENSE_REQUIRED: 'gold' as LicenseType, getI18nName: (i18n: any): string => { return i18n.translate('xpack.watcher.appName', { defaultMessage: 'Watcher', diff --git a/x-pack/plugins/watcher/server/types.ts b/x-pack/plugins/watcher/server/types.ts index 167dcb3ab64c3..5ef3aef7de1c6 100644 --- a/x-pack/plugins/watcher/server/types.ts +++ b/x-pack/plugins/watcher/server/types.ts @@ -8,8 +8,6 @@ import { IRouter } from 'kibana/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; import { LicensingPluginSetup } from '../../licensing/server'; -import { XPackMainPlugin } from '../../../legacy/plugins/xpack_main/server/xpack_main'; - export interface Dependencies { licensing: LicensingPluginSetup; features: FeaturesPluginSetup; @@ -18,7 +16,6 @@ export interface Dependencies { export interface ServerShim { route: any; plugins: { - xpack_main: XPackMainPlugin; watcher: any; }; } diff --git a/x-pack/legacy/common/eui_styled_components/eui_styled_components.tsx b/x-pack/plugins/xpack_legacy/common/eui_styled_components.tsx similarity index 100% rename from x-pack/legacy/common/eui_styled_components/eui_styled_components.tsx rename to x-pack/plugins/xpack_legacy/common/eui_styled_components.tsx diff --git a/x-pack/plugins/xpack_legacy/common/index.ts b/x-pack/plugins/xpack_legacy/common/index.ts new file mode 100644 index 0000000000000..8c0dace27faf4 --- /dev/null +++ b/x-pack/plugins/xpack_legacy/common/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './eui_styled_components'; diff --git a/x-pack/tasks/build.ts b/x-pack/tasks/build.ts index 41bf9587cad1e..a3b08a16f4b08 100644 --- a/x-pack/tasks/build.ts +++ b/x-pack/tasks/build.ts @@ -61,9 +61,6 @@ async function copySourceAndBabelify() { 'plugins/**/*', 'plugins/reporting/.phantom/*', 'plugins/reporting/.chromium/*', - 'legacy/common/**/*', - 'legacy/plugins/**/*', - 'legacy/server/**/*', 'typings/**/*', ], { diff --git a/x-pack/tasks/helpers/flags.ts b/x-pack/tasks/helpers/flags.ts index 33ee126c2a2ee..8820a4d60aa40 100644 --- a/x-pack/tasks/helpers/flags.ts +++ b/x-pack/tasks/helpers/flags.ts @@ -4,14 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { resolve } from 'path'; - import log from 'fancy-log'; import getopts from 'getopts'; -import { toArray } from 'rxjs/operators'; - -// @ts-ignore complicated module doesn't have types yet -import { findPluginSpecs } from '../../../src/legacy/plugin_discovery'; /* Usage: @@ -53,18 +47,3 @@ export const FLAGS = { .map((id) => id.trim()) : undefined, }; - -export async function getEnabledPlugins() { - if (FLAGS.plugins) { - return FLAGS.plugins; - } - - const { spec$ } = findPluginSpecs({ - plugins: { - paths: [resolve(__dirname, '..', '..')], - }, - }); - - const enabledPlugins: Array<{ getId: () => string }> = await spec$.pipe(toArray()).toPromise(); - return enabledPlugins.map((spec) => spec.getId()); -} diff --git a/x-pack/test/accessibility/apps/dashboard_edit_panel.ts b/x-pack/test/accessibility/apps/dashboard_edit_panel.ts new file mode 100644 index 0000000000000..1c3456ad8d593 --- /dev/null +++ b/x-pack/test/accessibility/apps/dashboard_edit_panel.ts @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const { dashboard } = getPageObjects(['dashboard']); + const a11y = getService('a11y'); + const dashboardPanelActions = getService('dashboardPanelActions'); + const testSubjects = getService('testSubjects'); + const esArchiver = getService('esArchiver'); + const drilldowns = getService('dashboardDrilldownsManage'); + const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['security', 'common']); + const toasts = getService('toasts'); + + describe('Dashboard Edit Panel', () => { + before(async () => { + await esArchiver.load('dashboard/drilldowns'); + await esArchiver.loadIfNeeded('logstash_functional'); + await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*' }); + await PageObjects.common.navigateToApp('dashboard'); + await dashboard.loadSavedDashboard(drilldowns.DASHBOARD_WITH_PIE_CHART_NAME); + await testSubjects.click('dashboardEditMode'); + }); + + after(async () => { + await esArchiver.unload('dashboard/drilldowns'); + }); + + // embeddable edit panel + it(' A11y test on dashboard edit panel menu options', async () => { + await testSubjects.click('embeddablePanelToggleMenuIcon'); + await a11y.testAppSnapshot(); + }); + + // https://github.com/elastic/kibana/issues/77931 + it.skip('A11y test for edit visualization and save', async () => { + await testSubjects.click('embeddablePanelToggleMenuIcon'); + await testSubjects.click('embeddablePanelAction-editPanel'); + await testSubjects.click('visualizesaveAndReturnButton'); + await a11y.testAppSnapshot(); + }); + + // clone panel + it(' A11y test on dashboard embeddable clone panel', async () => { + await testSubjects.click('embeddablePanelAction-clonePanel'); + await a11y.testAppSnapshot(); + await toasts.dismissAllToasts(); + await dashboardPanelActions.removePanelByTitle('Visualization PieChart (copy)'); + }); + + // edit panel title + it(' A11y test on dashboard embeddable edit dashboard title', async () => { + await testSubjects.click('embeddablePanelToggleMenuIcon'); + await testSubjects.click('embeddablePanelAction-ACTION_CUSTOMIZE_PANEL'); + await a11y.testAppSnapshot(); + await testSubjects.click('customizePanelHideTitle'); + await a11y.testAppSnapshot(); + await testSubjects.click('saveNewTitleButton'); + }); + + // create drilldown + it('A11y test on dashboard embeddable open flyout and drilldown', async () => { + await testSubjects.click('embeddablePanelToggleMenuIcon'); + await testSubjects.click('embeddablePanelAction-OPEN_FLYOUT_ADD_DRILLDOWN'); + await a11y.testAppSnapshot(); + await testSubjects.click('flyoutCloseButton'); + }); + + // clicking on more button + it('A11y test on dashboard embeddable more button', async () => { + await testSubjects.click('embeddablePanelToggleMenuIcon'); + await testSubjects.click('embeddablePanelMore-mainMenu'); + await a11y.testAppSnapshot(); + }); + + // https://github.com/elastic/kibana/issues/77422 + it.skip('A11y test on dashboard embeddable custom time range', async () => { + await testSubjects.click('embeddablePanelToggleMenuIcon'); + await testSubjects.click('embeddablePanelAction-CUSTOM_TIME_RANGE'); + await a11y.testAppSnapshot(); + }); + + // flow will change whenever the custom time range a11y issue gets fixed. + // Will need to click on gear icon and then click on more. + + // inspector panel + it('A11y test on dashboard embeddable open inspector', async () => { + await testSubjects.click('embeddablePanelAction-openInspector'); + await a11y.testAppSnapshot(); + await testSubjects.click('euiFlyoutCloseButton'); + }); + + // fullscreen + it('A11y test on dashboard embeddable fullscreen', async () => { + await testSubjects.click('embeddablePanelToggleMenuIcon'); + await testSubjects.click('embeddablePanelMore-mainMenu'); + await testSubjects.click('embeddablePanelAction-togglePanel'); + await a11y.testAppSnapshot(); + }); + + // minimize fullscreen panel + it('A11y test on dashboard embeddable fullscreen minimize ', async () => { + await testSubjects.click('embeddablePanelToggleMenuIcon'); + await testSubjects.click('embeddablePanelMore-mainMenu'); + await testSubjects.click('embeddablePanelAction-togglePanel'); + await a11y.testAppSnapshot(); + }); + + // replace panel + it('A11y test on dashboard embeddable replace panel', async () => { + await testSubjects.click('embeddablePanelToggleMenuIcon'); + await testSubjects.click('embeddablePanelMore-mainMenu'); + await testSubjects.click('embeddablePanelAction-replacePanel'); + await a11y.testAppSnapshot(); + await testSubjects.click('euiFlyoutCloseButton'); + }); + + // delete from dashboard + it('A11y test on dashboard embeddable delete panel', async () => { + await testSubjects.click('embeddablePanelToggleMenuIcon'); + await testSubjects.click('embeddablePanelMore-mainMenu'); + await testSubjects.click('embeddablePanelAction-deletePanel'); + await a11y.testAppSnapshot(); + }); + }); +} diff --git a/x-pack/test/accessibility/config.ts b/x-pack/test/accessibility/config.ts index bae7b688fd28c..1163b74b24628 100644 --- a/x-pack/test/accessibility/config.ts +++ b/x-pack/test/accessibility/config.ts @@ -21,7 +21,9 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('./apps/search_profiler'), require.resolve('./apps/uptime'), require.resolve('./apps/spaces'), + require.resolve('./apps/dashboard_edit_panel'), ], + pageObjects, services, diff --git a/x-pack/test/api_integration/apis/lens/telemetry.ts b/x-pack/test/api_integration/apis/lens/telemetry.ts index 0ae4753cd2967..5525a82b02ee8 100644 --- a/x-pack/test/api_integration/apis/lens/telemetry.ts +++ b/x-pack/test/api_integration/apis/lens/telemetry.ts @@ -60,7 +60,7 @@ export default ({ getService }: FtrProviderContext) => { it('should do nothing on empty post', async () => { await supertest - .post('/api/lens/telemetry') + .post('/api/lens/stats') .set(COMMON_HEADERS) .send({ events: {}, @@ -73,7 +73,7 @@ export default ({ getService }: FtrProviderContext) => { it('should write a document per results', async () => { await supertest - .post('/api/lens/telemetry') + .post('/api/lens/stats') .set(COMMON_HEADERS) .send({ events: { diff --git a/x-pack/test/api_integration/apis/maps/get_grid_tile.js b/x-pack/test/api_integration/apis/maps/get_grid_tile.js new file mode 100644 index 0000000000000..3eee56c962a27 --- /dev/null +++ b/x-pack/test/api_integration/apis/maps/get_grid_tile.js @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export default function ({ getService }) { + const supertest = getService('supertest'); + + describe('getGridTile', () => { + it('should validate params', async () => { + await supertest + .get( + `/api/maps/mvt/getGridTile?x=0&y=0&z=0&geometryFieldName=coordinates&index=logstash*&requestBody=(_source:(excludes:!()),aggs:(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:coordinates))),geotile_grid:(bounds:!n,field:coordinates,precision:!n,shard_size:65535,size:65535))),docvalue_fields:!((field:%27@timestamp%27,format:date_time),(field:timestamp,format:date_time),(field:utc_time,format:date_time)),query:(bool:(filter:!((match_all:()),(range:(timestamp:(format:strict_date_optional_time,gte:%272020-09-16T13:57:36.734Z%27,lte:%272020-09-23T13:57:36.734Z%27)))),must:!(),must_not:!(),should:!())),script_fields:(hour_of_day:(script:(lang:painless,source:%27doc[!%27timestamp!%27].value.getHour()%27))),size:0,stored_fields:!(%27*%27))&requestType=point&geoFieldType=geo_point` + ) + .set('kbn-xsrf', 'kibana') + .expect(200); + }); + + it('should not validate when required params are missing', async () => { + await supertest + .get( + `/api/maps/mvt/getGridTile?x=0&y=0&z=0&geometryFieldName=coordinates&index=logstash*&requestBody=(_source:(excludes:!()),aggs:(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:coordinates))),geotile_grid:(bounds:!n,field:coordinates,precision:!n,shard_size:65535,size:65535))),docvalue_fields:!((field:%27@timestamp%27,format:date_time),(field:timestamp,format:date_time),(field:utc_time,format:date_time)),query:(bool:(filter:!((match_all:()),(range:(timestamp:(format:strict_date_optional_time,gte:%272020-09-16T13:57:36.734Z%27,lte:%272020-09-23T13:57:36.734Z%27)))),must:!(),must_not:!(),should:!())),script_fields:(hour_of_day:(script:(lang:painless,source:%27doc[!%27timestamp!%27].value.getHour()%27))),size:0,stored_fields:!(%27*%27))&requestType=point` + ) + .set('kbn-xsrf', 'kibana') + .expect(400); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/maps/index.js b/x-pack/test/api_integration/apis/maps/index.js index 6c213380dd64e..9f1fe96c5637b 100644 --- a/x-pack/test/api_integration/apis/maps/index.js +++ b/x-pack/test/api_integration/apis/maps/index.js @@ -17,6 +17,7 @@ export default function ({ loadTestFile, getService }) { loadTestFile(require.resolve('./index_settings')); loadTestFile(require.resolve('./migrations')); loadTestFile(require.resolve('./get_tile')); + loadTestFile(require.resolve('./get_grid_tile')); }); }); } diff --git a/x-pack/test/api_integration/apis/security_solution/index.js b/x-pack/test/api_integration/apis/security_solution/index.js index b97795f325271..a9ddf091245f7 100644 --- a/x-pack/test/api_integration/apis/security_solution/index.js +++ b/x-pack/test/api_integration/apis/security_solution/index.js @@ -12,17 +12,17 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./kpi_hosts')); loadTestFile(require.resolve('./network_dns')); loadTestFile(require.resolve('./network_top_n_flow')); - loadTestFile(require.resolve('./overview_host')); + // loadTestFile(require.resolve('./overview_host')); loadTestFile(require.resolve('./saved_objects/notes')); loadTestFile(require.resolve('./saved_objects/pinned_events')); loadTestFile(require.resolve('./saved_objects/timeline')); loadTestFile(require.resolve('./sources')); - loadTestFile(require.resolve('./overview_network')); + // loadTestFile(require.resolve('./overview_network')); loadTestFile(require.resolve('./timeline')); loadTestFile(require.resolve('./timeline_details')); - loadTestFile(require.resolve('./uncommon_processes')); + // loadTestFile(require.resolve('./uncommon_processes')); loadTestFile(require.resolve('./users')); - loadTestFile(require.resolve('./tls')); + // loadTestFile(require.resolve('./tls')); loadTestFile(require.resolve('./feature_controls')); }); } diff --git a/x-pack/test/api_integration/apis/security_solution/overview_host.ts b/x-pack/test/api_integration/apis/security_solution/overview_host.ts index ffbf9d89fc112..0d648e665a9a9 100644 --- a/x-pack/test/api_integration/apis/security_solution/overview_host.ts +++ b/x-pack/test/api_integration/apis/security_solution/overview_host.ts @@ -7,7 +7,9 @@ import expect from '@kbn/expect'; import { DEFAULT_INDEX_PATTERN } from '../../../../plugins/security_solution/common/constants'; +// @ts-expect-error import { overviewHostQuery } from '../../../../plugins/security_solution/public/overview/containers//overview_host/index.gql_query'; +// @ts-expect-error import { GetOverviewHostQuery } from '../../../../plugins/security_solution/public/graphql/types'; import { FtrProviderContext } from '../../ftr_provider_context'; diff --git a/x-pack/test/api_integration/apis/security_solution/overview_network.ts b/x-pack/test/api_integration/apis/security_solution/overview_network.ts index 6976b225a4d2a..60d300e168e4a 100644 --- a/x-pack/test/api_integration/apis/security_solution/overview_network.ts +++ b/x-pack/test/api_integration/apis/security_solution/overview_network.ts @@ -5,7 +5,9 @@ */ import expect from '@kbn/expect'; +// @ts-expect-error import { overviewNetworkQuery } from '../../../../plugins/security_solution/public/overview/containers/overview_network/index.gql_query'; +// @ts-expect-error import { GetOverviewNetworkQuery } from '../../../../plugins/security_solution/public/graphql/types'; import { FtrProviderContext } from '../../ftr_provider_context'; diff --git a/x-pack/test/api_integration/apis/security_solution/sources.ts b/x-pack/test/api_integration/apis/security_solution/sources.ts index f99dd4c65fc83..1ec4bfda8492d 100644 --- a/x-pack/test/api_integration/apis/security_solution/sources.ts +++ b/x-pack/test/api_integration/apis/security_solution/sources.ts @@ -5,110 +5,107 @@ */ import expect from '@kbn/expect'; -import { sourceQuery } from '../../../../plugins/security_solution/public/common/containers/source/index.gql_query'; -import { SourceQuery } from '../../../../plugins/security_solution/public/graphql/types'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); - const client = getService('securitySolutionGraphQLClient'); + const supertest = getService('supertest'); describe('sources', () => { before(() => esArchiver.load('auditbeat/default')); after(() => esArchiver.unload('auditbeat/default')); it('Make sure that we get source information when auditbeat indices is there', async () => { - const resp = await client.query({ - query: sourceQuery, - variables: { - sourceId: 'default', - defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - docValueFields: [], - }, - }); - const sourceStatus = resp.data.source.status; - // test data in x-pack/test/functional/es_archives/auditbeat_test_data/data.json.gz - expect(sourceStatus.indexFields.length).to.be(397); - expect(sourceStatus.indicesExist).to.be(true); + const { body: sourceStatus } = await supertest + .post('/internal/search/securitySolutionIndexFields/') + .set('kbn-xsrf', 'true') + .send({ + indices: ['auditbeat-*'], + onlyCheckIfIndicesExist: false, + }) + .expect(200); + + expect(sourceStatus.indexFields.length).to.be(351); + expect(sourceStatus.indicesExist).to.eql(['auditbeat-*']); }); it('should find indexes as being available when they exist', async () => { - const resp = await client.query({ - query: sourceQuery, - variables: { - sourceId: 'default', - defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - docValueFields: [], - }, - }); - const sourceStatus = resp.data.source.status; - expect(sourceStatus.indicesExist).to.be(true); + const { body: sourceStatus } = await supertest + .post('/internal/search/securitySolutionIndexFields/') + .set('kbn-xsrf', 'true') + .send({ + indices: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + onlyCheckIfIndicesExist: false, + }) + .expect(200); + + expect(sourceStatus.indicesExist).to.eql(['auditbeat-*', 'winlogbeat-*']); }); it('should not find indexes as existing when there is an empty array of them', async () => { - const resp = await client.query({ - query: sourceQuery, - variables: { - sourceId: 'default', - defaultIndex: [], - docValueFields: [], - }, - }); - const sourceStatus = resp.data.source.status; - expect(sourceStatus.indicesExist).to.be(false); + const { body: sourceStatus } = await supertest + .post('/internal/search/securitySolutionIndexFields/') + .set('kbn-xsrf', 'true') + .send({ + indices: [], + onlyCheckIfIndicesExist: false, + }) + .expect(200); + + expect(sourceStatus.indicesExist).to.eql([]); }); it('should not find indexes as existing when there is a _all within it', async () => { - const resp = await client.query({ - query: sourceQuery, - variables: { - sourceId: 'default', - defaultIndex: ['_all'], - docValueFields: [], - }, - }); - const sourceStatus = resp.data.source.status; - expect(sourceStatus.indicesExist).to.be(false); + const { body: sourceStatus } = await supertest + .post('/internal/search/securitySolutionIndexFields/') + .set('kbn-xsrf', 'true') + .send({ + indices: ['_all'], + onlyCheckIfIndicesExist: false, + }) + .expect(200); + + expect(sourceStatus.indicesExist).to.eql([]); }); it('should not find indexes as existing when there are empty strings within it', async () => { - const resp = await client.query({ - query: sourceQuery, - variables: { - sourceId: 'default', - defaultIndex: [''], - docValueFields: [], - }, - }); - const sourceStatus = resp.data.source.status; - expect(sourceStatus.indicesExist).to.be(false); + const { body: sourceStatus } = await supertest + .post('/internal/search/securitySolutionIndexFields/') + .set('kbn-xsrf', 'true') + .send({ + indices: [''], + onlyCheckIfIndicesExist: false, + }) + .expect(200); + + expect(sourceStatus.indicesExist).to.eql([]); }); it('should not find indexes as existing when there are blank spaces within it', async () => { - const resp = await client.query({ - query: sourceQuery, - variables: { - sourceId: 'default', - defaultIndex: [' '], - docValueFields: [], - }, - }); - const sourceStatus = resp.data.source.status; - expect(sourceStatus.indicesExist).to.be(false); + const { body: sourceStatus } = await supertest + .post('/internal/search/securitySolutionIndexFields/') + .set('kbn-xsrf', 'true') + .send({ + indices: [' '], + onlyCheckIfIndicesExist: false, + }) + .expect(200); + + expect(sourceStatus.indicesExist).to.eql([]); }); it('should find indexes when one is an empty index but the others are valid', async () => { - const resp = await client.query({ - query: sourceQuery, - variables: { - sourceId: 'default', - defaultIndex: ['', 'auditbeat-*'], - docValueFields: [], - }, - }); - const sourceStatus = resp.data.source.status; - expect(sourceStatus.indicesExist).to.be(true); + const { body: sourceStatus } = await supertest + .post('/internal/search/securitySolutionIndexFields/') + .set('kbn-xsrf', 'true') + .send({ + indices: ['', 'auditbeat-*'], + onlyCheckIfIndicesExist: false, + }) + .expect(200); + + expect(sourceStatus.indicesExist).to.eql(['auditbeat-*']); }); }); } diff --git a/x-pack/test/api_integration/apis/security_solution/tls.ts b/x-pack/test/api_integration/apis/security_solution/tls.ts index e5f6233d50d59..ebaec7783427f 100644 --- a/x-pack/test/api_integration/apis/security_solution/tls.ts +++ b/x-pack/test/api_integration/apis/security_solution/tls.ts @@ -5,11 +5,14 @@ */ import expect from '@kbn/expect'; +// @ts-expect-error import { tlsQuery } from '../../../../plugins/security_solution/public/network/containers/tls/index.gql_query'; import { Direction, + // @ts-expect-error TlsFields, FlowTarget, + // @ts-expect-error GetTlsQuery, } from '../../../../plugins/security_solution/public/graphql/types'; import { FtrProviderContext } from '../../ftr_provider_context'; diff --git a/x-pack/test/api_integration/apis/security_solution/uncommon_processes.ts b/x-pack/test/api_integration/apis/security_solution/uncommon_processes.ts index f1e064bcc37bb..1ed9a03ecf87e 100644 --- a/x-pack/test/api_integration/apis/security_solution/uncommon_processes.ts +++ b/x-pack/test/api_integration/apis/security_solution/uncommon_processes.ts @@ -6,7 +6,9 @@ import expect from '@kbn/expect'; +// @ts-expect-error import { uncommonProcessesQuery } from '../../../../plugins/security_solution/public/hosts/containers/uncommon_processes/index.gql_query'; +// @ts-expect-error import { GetUncommonProcessesQuery } from '../../../../plugins/security_solution/public/graphql/types'; import { FtrProviderContext } from '../../ftr_provider_context'; diff --git a/x-pack/test/apm_api_integration/basic/tests/metrics_charts/metrics_charts.ts b/x-pack/test/apm_api_integration/basic/tests/metrics_charts/metrics_charts.ts index ad3d1b0ccc4d9..cae562b3f5dc5 100644 --- a/x-pack/test/apm_api_integration/basic/tests/metrics_charts/metrics_charts.ts +++ b/x-pack/test/apm_api_integration/basic/tests/metrics_charts/metrics_charts.ts @@ -19,8 +19,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - // FLAKY: https://github.com/elastic/kibana/issues/77870 - describe.skip('when data is loaded', () => { + describe('when data is loaded', () => { before(() => esArchiver.load('metrics_8.0.0')); after(() => esArchiver.unload('metrics_8.0.0')); @@ -70,7 +69,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .toMatchInline(` Array [ 0.714, - 0.38770000000000004, + 0.3877, 0.75, 0.2543, ] @@ -100,8 +99,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { expectSnapshot(systemMemoryUsageChart?.series.map(({ overallValue }) => overallValue)) .toMatchInline(` Array [ - 0.7220939209255549, - 0.7181735467963479, + 0.722093920925555, + 0.718173546796348, ] `); }); @@ -162,9 +161,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { .toMatchInline(` Array [ 0.203, - 0.17877777777777779, + 0.178777777777778, 0.01, - 0.009000000000000001, + 0.009, ] `); }); @@ -175,8 +174,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { Array [ 0.193, 0.193, - 0.009000000000000001, - 0.009000000000000001, + 0.009, + 0.009, ] `); }); @@ -204,8 +203,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { expectSnapshot(systemMemoryUsageChart?.series.map(({ overallValue }) => overallValue)) .toMatchInline(` Array [ - 0.7079247035578369, - 0.7053959808411816, + 0.707924703557837, + 0.705395980841182, ] `); }); @@ -214,8 +213,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { const yValues = systemMemoryUsageChart?.series.map((serie) => first(serie.data)?.y); expectSnapshot(yValues).toMatchInline(` Array [ - 0.7079247035578369, - 0.7079247035578369, + 0.707924703557837, + 0.707924703557837, ] `); }); @@ -244,7 +243,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) .toMatchInline(` Array [ - 222501617.7777778, + 222501617.777778, 374341632, 1560281088, ] @@ -285,8 +284,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) .toMatchInline(` Array [ - 138573397.33333334, - 147677639.1111111, + 138573397.333333, + 147677639.111111, ] `); }); @@ -324,7 +323,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) .toMatchInline(` Array [ - 44.44444444444444, + 44.4444444444444, 45, ] `); @@ -423,16 +422,16 @@ export default function ApiTest({ getService }: FtrProviderContext) { expectSnapshot(systemMemoryUsageChart?.series.map(({ overallValue }) => overallValue)) .toMatchInline(` Array [ - 0.11452389642649889, - 0.11400237609041514, + 0.114523896426499, + 0.114002376090415, ] `); const yValues = systemMemoryUsageChart?.series.map((serie) => first(serie.data)?.y); expectSnapshot(yValues).toMatchInline(` Array [ - 0.11383724014063981, - 0.11383724014063981, + 0.11383724014064, + 0.11383724014064, ] `); }); diff --git a/x-pack/test/apm_api_integration/basic/tests/observability_overview/observability_overview.ts b/x-pack/test/apm_api_integration/basic/tests/observability_overview/observability_overview.ts index 5b04213401660..41564af55562a 100644 --- a/x-pack/test/apm_api_integration/basic/tests/observability_overview/observability_overview.ts +++ b/x-pack/test/apm_api_integration/basic/tests/observability_overview/observability_overview.ts @@ -64,15 +64,15 @@ export default function ApiTest({ getService }: FtrProviderContext) { }, Object { "x": "2020-09-15T08:54:00.000Z", - "y": 1.8666666666666667, + "y": 1.86666666666667, }, Object { "x": "2020-09-15T08:55:00.000Z", - "y": 0.9666666666666667, + "y": 0.966666666666667, }, Object { "x": "2020-09-15T08:56:00.000Z", - "y": 1.9333333333333333, + "y": 1.93333333333333, }, Object { "x": "2020-09-15T08:57:00.000Z", diff --git a/x-pack/test/apm_api_integration/basic/tests/services/top_services.ts b/x-pack/test/apm_api_integration/basic/tests/services/top_services.ts index 9eb9d80e26b6c..0e0d5cb21b71a 100644 --- a/x-pack/test/apm_api_integration/basic/tests/services/top_services.ts +++ b/x-pack/test/apm_api_integration/basic/tests/services/top_services.ts @@ -91,43 +91,43 @@ export default function ApiTest({ getService }: FtrProviderContext) { Array [ Object { "avgResponseTime": Object { - "value": 213583.7652495379, + "value": 213583.765249538, }, "transactionErrorRate": Object { "value": 0, }, "transactionsPerMinute": Object { - "value": 18.033333333333335, + "value": 18.0333333333333, }, }, Object { "avgResponseTime": Object { - "value": 600255.7079646018, + "value": 600255.707964602, }, "transactionErrorRate": Object { "value": 0, }, "transactionsPerMinute": Object { - "value": 7.533333333333333, + "value": 7.53333333333333, }, }, Object { "avgResponseTime": Object { - "value": 1818501.060810811, + "value": 1818501.06081081, }, "transactionErrorRate": Object { - "value": 0.02027027027027027, + "value": 0.0202702702702703, }, "transactionsPerMinute": Object { - "value": 4.933333333333334, + "value": 4.93333333333333, }, }, Object { "avgResponseTime": Object { - "value": 290900.5714285714, + "value": 290900.571428571, }, "transactionErrorRate": Object { - "value": 0.013605442176870748, + "value": 0.0136054421768707, }, "transactionsPerMinute": Object { "value": 4.9, @@ -135,10 +135,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }, Object { "avgResponseTime": Object { - "value": 1123903.7027027027, + "value": 1123903.7027027, }, "transactionErrorRate": Object { - "value": 0.009009009009009009, + "value": 0.00900900900900901, }, "transactionsPerMinute": Object { "value": 3.7, @@ -146,10 +146,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }, Object { "avgResponseTime": Object { - "value": 80364.62962962964, + "value": 80364.6296296296, }, "transactionErrorRate": Object { - "value": 0.18518518518518517, + "value": 0.185185185185185, }, "transactionsPerMinute": Object { "value": 3.6, @@ -157,10 +157,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }, Object { "avgResponseTime": Object { - "value": 1365102.9411764706, + "value": 1365102.94117647, }, "transactionsPerMinute": Object { - "value": 2.2666666666666666, + "value": 2.26666666666667, }, }, ] diff --git a/x-pack/test/apm_api_integration/basic/tests/traces/__snapshots__/top_traces.snap b/x-pack/test/apm_api_integration/basic/tests/traces/__snapshots__/top_traces.snap index cd5773d18d6b7..157bbccd109be 100644 --- a/x-pack/test/apm_api_integration/basic/tests/traces/__snapshots__/top_traces.snap +++ b/x-pack/test/apm_api_integration/basic/tests/traces/__snapshots__/top_traces.snap @@ -12,11 +12,11 @@ Array [ "serviceName": "opbeans-node", "transactionName": "POST /api/orders", "transactionType": "request", - "transactionsPerMinute": 0.03333333333333333, + "transactionsPerMinute": 0.0333333333333333, }, Object { "averageResponseTime": 3347, - "impact": 0.003559081182448518, + "impact": 0.00355908118244852, "key": Object { "service.name": "opbeans-python", "transaction.name": "GET opbeans.views.stats", @@ -24,7 +24,7 @@ Array [ "serviceName": "opbeans-python", "transactionName": "GET opbeans.views.stats", "transactionType": "request", - "transactionsPerMinute": 0.03333333333333333, + "transactionsPerMinute": 0.0333333333333333, }, Object { "averageResponseTime": 4479, @@ -36,11 +36,11 @@ Array [ "serviceName": "opbeans-node", "transactionName": "GET /api/customers/:id", "transactionType": "request", - "transactionsPerMinute": 0.03333333333333333, + "transactionsPerMinute": 0.0333333333333333, }, Object { "averageResponseTime": 7287, - "impact": 0.009904230439845424, + "impact": 0.00990423043984542, "key": Object { "service.name": "opbeans-node", "transaction.name": "GET /api/products/top", @@ -48,11 +48,11 @@ Array [ "serviceName": "opbeans-node", "transactionName": "GET /api/products/top", "transactionType": "request", - "transactionsPerMinute": 0.03333333333333333, + "transactionsPerMinute": 0.0333333333333333, }, Object { "averageResponseTime": 8023, - "impact": 0.011089517204678958, + "impact": 0.011089517204679, "key": Object { "service.name": "opbeans-ruby", "transaction.name": "Api::OrdersController#show", @@ -60,11 +60,11 @@ Array [ "serviceName": "opbeans-ruby", "transactionName": "Api::OrdersController#show", "transactionType": "request", - "transactionsPerMinute": 0.03333333333333333, + "transactionsPerMinute": 0.0333333333333333, }, Object { "averageResponseTime": 8282, - "impact": 0.011506622193934236, + "impact": 0.0115066221939342, "key": Object { "service.name": "opbeans-node", "transaction.name": "GET /api/orders/:id", @@ -72,11 +72,11 @@ Array [ "serviceName": "opbeans-node", "transactionName": "GET /api/orders/:id", "transactionType": "request", - "transactionsPerMinute": 0.03333333333333333, + "transactionsPerMinute": 0.0333333333333333, }, Object { "averageResponseTime": 12116, - "impact": 0.017681064390091532, + "impact": 0.0176810643900915, "key": Object { "service.name": "opbeans-ruby", "transaction.name": "Api::ProductsController#top", @@ -84,11 +84,11 @@ Array [ "serviceName": "opbeans-ruby", "transactionName": "Api::ProductsController#top", "transactionType": "request", - "transactionsPerMinute": 0.03333333333333333, + "transactionsPerMinute": 0.0333333333333333, }, Object { "averageResponseTime": 6451, - "impact": 0.018946873353622995, + "impact": 0.018946873353623, "key": Object { "service.name": "opbeans-node", "transaction.name": "GET /api/products", @@ -96,11 +96,11 @@ Array [ "serviceName": "opbeans-node", "transactionName": "GET /api/products", "transactionType": "request", - "transactionsPerMinute": 0.06666666666666667, + "transactionsPerMinute": 0.0666666666666667, }, Object { "averageResponseTime": 13360, - "impact": 0.019684456693696034, + "impact": 0.019684456693696, "key": Object { "service.name": "opbeans-java", "transaction.name": "APIRestController#customers", @@ -108,11 +108,11 @@ Array [ "serviceName": "opbeans-java", "transactionName": "APIRestController#customers", "transactionType": "request", - "transactionsPerMinute": 0.03333333333333333, + "transactionsPerMinute": 0.0333333333333333, }, Object { "averageResponseTime": 7903, - "impact": 0.023623602653998786, + "impact": 0.0236236026539988, "key": Object { "service.name": "opbeans-java", "transaction.name": "APIRestController#product", @@ -120,11 +120,11 @@ Array [ "serviceName": "opbeans-java", "transactionName": "APIRestController#product", "transactionType": "request", - "transactionsPerMinute": 0.06666666666666667, + "transactionsPerMinute": 0.0666666666666667, }, Object { "averageResponseTime": 17913, - "impact": 0.027016808107129565, + "impact": 0.0270168081071296, "key": Object { "service.name": "opbeans-node", "transaction.name": "GET /api/stats", @@ -132,11 +132,11 @@ Array [ "serviceName": "opbeans-node", "transactionName": "GET /api/stats", "transactionType": "request", - "transactionsPerMinute": 0.03333333333333333, + "transactionsPerMinute": 0.0333333333333333, }, Object { - "averageResponseTime": 6065.666666666667, - "impact": 0.02747417419573381, + "averageResponseTime": 6065.66666666667, + "impact": 0.0274741741957338, "key": Object { "service.name": "opbeans-java", "transaction.name": "APIRestController#topProducts", @@ -148,7 +148,7 @@ Array [ }, Object { "averageResponseTime": 2340.875, - "impact": 0.02832770950193187, + "impact": 0.0283277095019319, "key": Object { "service.name": "opbeans-java", "transaction.name": "ResourceHttpRequestHandler", @@ -156,11 +156,11 @@ Array [ "serviceName": "opbeans-java", "transactionName": "ResourceHttpRequestHandler", "transactionType": "request", - "transactionsPerMinute": 0.26666666666666666, + "transactionsPerMinute": 0.266666666666667, }, Object { - "averageResponseTime": 7340.666666666667, - "impact": 0.03363412239612548, + "averageResponseTime": 7340.66666666667, + "impact": 0.0336341223961255, "key": Object { "service.name": "opbeans-java", "transaction.name": "APIRestController#customerWhoBought", @@ -172,7 +172,7 @@ Array [ }, Object { "averageResponseTime": 7689, - "impact": 0.03531703634891222, + "impact": 0.0353170363489122, "key": Object { "service.name": "opbeans-node", "transaction.name": "GET /api/types", @@ -184,7 +184,7 @@ Array [ }, Object { "averageResponseTime": 11598, - "impact": 0.035524783621552876, + "impact": 0.0355247836215529, "key": Object { "service.name": "opbeans-node", "transaction.name": "GET /api/products/:id/customers", @@ -192,11 +192,11 @@ Array [ "serviceName": "opbeans-node", "transactionName": "GET /api/products/:id/customers", "transactionType": "request", - "transactionsPerMinute": 0.06666666666666667, + "transactionsPerMinute": 0.0666666666666667, }, Object { "averageResponseTime": 12077.5, - "impact": 0.03706919939257919, + "impact": 0.0370691993925792, "key": Object { "service.name": "opbeans-java", "transaction.name": "APIRestController#order", @@ -204,11 +204,11 @@ Array [ "serviceName": "opbeans-java", "transactionName": "APIRestController#order", "transactionType": "request", - "transactionsPerMinute": 0.06666666666666667, + "transactionsPerMinute": 0.0666666666666667, }, Object { "averageResponseTime": 6296.5, - "impact": 0.03872956712973051, + "impact": 0.0387295671297305, "key": Object { "service.name": "opbeans-ruby", "transaction.name": "Api::TypesController#index", @@ -216,11 +216,11 @@ Array [ "serviceName": "opbeans-ruby", "transactionName": "Api::TypesController#index", "transactionType": "request", - "transactionsPerMinute": 0.13333333333333333, + "transactionsPerMinute": 0.133333333333333, }, Object { "averageResponseTime": 28181, - "impact": 0.04355284683173653, + "impact": 0.0435528468317365, "key": Object { "service.name": "opbeans-python", "transaction.name": "GET opbeans.views.customer", @@ -228,11 +228,11 @@ Array [ "serviceName": "opbeans-python", "transactionName": "GET opbeans.views.customer", "transactionType": "request", - "transactionsPerMinute": 0.03333333333333333, + "transactionsPerMinute": 0.0333333333333333, }, Object { "averageResponseTime": 7439, - "impact": 0.046089296090721335, + "impact": 0.0460892960907213, "key": Object { "service.name": "opbeans-go", "transaction.name": "GET /api/customers/:id", @@ -240,10 +240,10 @@ Array [ "serviceName": "opbeans-go", "transactionName": "GET /api/customers/:id", "transactionType": "request", - "transactionsPerMinute": 0.13333333333333333, + "transactionsPerMinute": 0.133333333333333, }, Object { - "averageResponseTime": 10471.333333333334, + "averageResponseTime": 10471.3333333333, "impact": 0.0487594121995447, "key": Object { "service.name": "opbeans-node", @@ -264,11 +264,11 @@ Array [ "serviceName": "opbeans-node", "transactionName": "GET /api/customers", "transactionType": "request", - "transactionsPerMinute": 0.06666666666666667, + "transactionsPerMinute": 0.0666666666666667, }, Object { "averageResponseTime": 11732.25, - "impact": 0.07374545045551247, + "impact": 0.0737454504555125, "key": Object { "service.name": "opbeans-java", "transaction.name": "APIRestController#customer", @@ -276,7 +276,7 @@ Array [ "serviceName": "opbeans-java", "transactionName": "APIRestController#customer", "transactionType": "request", - "transactionsPerMinute": 0.13333333333333333, + "transactionsPerMinute": 0.133333333333333, }, Object { "averageResponseTime": 47646, @@ -288,11 +288,11 @@ Array [ "serviceName": "opbeans-python", "transactionName": "GET opbeans.views.customers", "transactionType": "request", - "transactionsPerMinute": 0.03333333333333333, + "transactionsPerMinute": 0.0333333333333333, }, Object { "averageResponseTime": 13160.75, - "impact": 0.08294752732271193, + "impact": 0.0829475273227119, "key": Object { "service.name": "opbeans-python", "transaction.name": "GET opbeans.views.products", @@ -300,11 +300,11 @@ Array [ "serviceName": "opbeans-python", "transactionName": "GET opbeans.views.products", "transactionType": "request", - "transactionsPerMinute": 0.13333333333333333, + "transactionsPerMinute": 0.133333333333333, }, Object { - "averageResponseTime": 4131.461538461538, - "impact": 0.08466426059895181, + "averageResponseTime": 4131.46153846154, + "impact": 0.0846642605989518, "key": Object { "service.name": "opbeans-go", "transaction.name": "GET /api/types/:id", @@ -312,11 +312,11 @@ Array [ "serviceName": "opbeans-go", "transactionName": "GET /api/types/:id", "transactionType": "request", - "transactionsPerMinute": 0.43333333333333335, + "transactionsPerMinute": 0.433333333333333, }, Object { "averageResponseTime": 13869.25, - "impact": 0.08751152554491062, + "impact": 0.0875115255449106, "key": Object { "service.name": "opbeans-ruby", "transaction.name": "Api::StatsController#index", @@ -324,11 +324,11 @@ Array [ "serviceName": "opbeans-ruby", "transactionName": "Api::StatsController#index", "transactionType": "request", - "transactionsPerMinute": 0.13333333333333333, + "transactionsPerMinute": 0.133333333333333, }, Object { - "averageResponseTime": 20643.333333333332, - "impact": 0.09790372050886552, + "averageResponseTime": 20643.3333333333, + "impact": 0.0979037205088655, "key": Object { "service.name": "opbeans-ruby", "transaction.name": "Api::ProductsController#show", @@ -340,7 +340,7 @@ Array [ }, Object { "averageResponseTime": 15596.5, - "impact": 0.09863808296099064, + "impact": 0.0986380829609906, "key": Object { "service.name": "opbeans-ruby", "transaction.name": "Api::TypesController#show", @@ -348,11 +348,11 @@ Array [ "serviceName": "opbeans-ruby", "transactionName": "Api::TypesController#show", "transactionType": "request", - "transactionsPerMinute": 0.13333333333333333, + "transactionsPerMinute": 0.133333333333333, }, Object { "averageResponseTime": 20989, - "impact": 0.09957375090986059, + "impact": 0.0995737509098606, "key": Object { "service.name": "opbeans-python", "transaction.name": "GET opbeans.views.orders", @@ -364,7 +364,7 @@ Array [ }, Object { "averageResponseTime": 74419, - "impact": 0.11801655529963453, + "impact": 0.118016555299635, "key": Object { "service.name": "opbeans-python", "transaction.name": "GET opbeans.views.product_type", @@ -372,11 +372,11 @@ Array [ "serviceName": "opbeans-python", "transactionName": "GET opbeans.views.product_type", "transactionType": "request", - "transactionsPerMinute": 0.03333333333333333, + "transactionsPerMinute": 0.0333333333333333, }, Object { - "averageResponseTime": 10678.42857142857, - "impact": 0.11854800181104089, + "averageResponseTime": 10678.4285714286, + "impact": 0.118548001811041, "key": Object { "service.name": "opbeans-go", "transaction.name": "GET /api/orders/:id", @@ -384,11 +384,11 @@ Array [ "serviceName": "opbeans-go", "transactionName": "GET /api/orders/:id", "transactionType": "request", - "transactionsPerMinute": 0.23333333333333334, + "transactionsPerMinute": 0.233333333333333, }, Object { - "averageResponseTime": 27078.666666666668, - "impact": 0.12899495187011034, + "averageResponseTime": 27078.6666666667, + "impact": 0.12899495187011, "key": Object { "service.name": "opbeans-ruby", "transaction.name": "Api::OrdersController#index", @@ -399,8 +399,8 @@ Array [ "transactionsPerMinute": 0.1, }, Object { - "averageResponseTime": 11827.42857142857, - "impact": 0.13150080269358994, + "averageResponseTime": 11827.4285714286, + "impact": 0.13150080269359, "key": Object { "service.name": "opbeans-go", "transaction.name": "GET /api/customers", @@ -408,11 +408,11 @@ Array [ "serviceName": "opbeans-go", "transactionName": "GET /api/customers", "transactionType": "request", - "transactionsPerMinute": 0.23333333333333334, + "transactionsPerMinute": 0.233333333333333, }, Object { "averageResponseTime": 21770.75, - "impact": 0.13841121778584634, + "impact": 0.138411217785846, "key": Object { "service.name": "opbeans-python", "transaction.name": "GET opbeans.views.product", @@ -420,11 +420,11 @@ Array [ "serviceName": "opbeans-python", "transactionName": "GET opbeans.views.product", "transactionType": "request", - "transactionsPerMinute": 0.13333333333333333, + "transactionsPerMinute": 0.133333333333333, }, Object { "averageResponseTime": 10252, - "impact": 0.1467613697908217, + "impact": 0.146761369790822, "key": Object { "service.name": "opbeans-go", "transaction.name": "GET /api/types", @@ -436,7 +436,7 @@ Array [ }, Object { "averageResponseTime": 100570, - "impact": 0.16013127566262603, + "impact": 0.160131275662626, "key": Object { "service.name": "opbeans-python", "transaction.name": "GET opbeans.views.top_products", @@ -444,11 +444,11 @@ Array [ "serviceName": "opbeans-python", "transactionName": "GET opbeans.views.top_products", "transactionType": "request", - "transactionsPerMinute": 0.03333333333333333, + "transactionsPerMinute": 0.0333333333333333, }, Object { "averageResponseTime": 15505, - "impact": 0.1979283957314345, + "impact": 0.197928395731435, "key": Object { "service.name": "opbeans-ruby", "transaction.name": "Api::CustomersController#index", @@ -456,11 +456,11 @@ Array [ "serviceName": "opbeans-ruby", "transactionName": "Api::CustomersController#index", "transactionType": "request", - "transactionsPerMinute": 0.26666666666666666, + "transactionsPerMinute": 0.266666666666667, }, Object { "averageResponseTime": 22856.5, - "impact": 0.21902360134631826, + "impact": 0.219023601346318, "key": Object { "service.name": "opbeans-go", "transaction.name": "GET /api/products", @@ -472,7 +472,7 @@ Array [ }, Object { "averageResponseTime": 17250.125, - "impact": 0.2204118040518706, + "impact": 0.220411804051871, "key": Object { "service.name": "opbeans-ruby", "transaction.name": "Api::ProductsController#index", @@ -480,11 +480,11 @@ Array [ "serviceName": "opbeans-ruby", "transactionName": "Api::ProductsController#index", "transactionType": "request", - "transactionsPerMinute": 0.26666666666666666, + "transactionsPerMinute": 0.266666666666667, }, Object { - "averageResponseTime": 20089.555555555555, - "impact": 0.2893468583571687, + "averageResponseTime": 20089.5555555556, + "impact": 0.289346858357169, "key": Object { "service.name": "opbeans-ruby", "transaction.name": "Api::CustomersController#show", @@ -495,8 +495,8 @@ Array [ "transactionsPerMinute": 0.3, }, Object { - "averageResponseTime": 26487.85714285714, - "impact": 0.29676939463314395, + "averageResponseTime": 26487.8571428571, + "impact": 0.296769394633144, "key": Object { "service.name": "opbeans-go", "transaction.name": "GET /api/stats", @@ -504,11 +504,11 @@ Array [ "serviceName": "opbeans-go", "transactionName": "GET /api/stats", "transactionType": "request", - "transactionsPerMinute": 0.23333333333333334, + "transactionsPerMinute": 0.233333333333333, }, Object { - "averageResponseTime": 14957.538461538461, - "impact": 0.31131653504991197, + "averageResponseTime": 14957.5384615385, + "impact": 0.311316535049912, "key": Object { "service.name": "opbeans-go", "transaction.name": "GET /api/products/:id/customers", @@ -516,7 +516,7 @@ Array [ "serviceName": "opbeans-go", "transactionName": "GET /api/products/:id/customers", "transactionType": "request", - "transactionsPerMinute": 0.43333333333333335, + "transactionsPerMinute": 0.433333333333333, }, Object { "averageResponseTime": 30178.5, @@ -528,11 +528,11 @@ Array [ "serviceName": "opbeans-node", "transactionName": "GET /api", "transactionType": "request", - "transactionsPerMinute": 0.4666666666666667, + "transactionsPerMinute": 0.466666666666667, }, Object { "averageResponseTime": 32625.875, - "impact": 0.8388432258236366, + "impact": 0.838843225823637, "key": Object { "service.name": "opbeans-go", "transaction.name": "GET /api/products/:id", @@ -540,11 +540,11 @@ Array [ "serviceName": "opbeans-go", "transactionName": "GET /api/products/:id", "transactionType": "request", - "transactionsPerMinute": 0.5333333333333333, + "transactionsPerMinute": 0.533333333333333, }, Object { - "averageResponseTime": 121200.83333333333, - "impact": 1.1692918352841768, + "averageResponseTime": 121200.833333333, + "impact": 1.16929183528418, "key": Object { "service.name": "opbeans-python", "transaction.name": "GET opbeans.views.product_customers", @@ -555,8 +555,8 @@ Array [ "transactionsPerMinute": 0.2, }, Object { - "averageResponseTime": 38025.86666666667, - "impact": 3.6724805948748136, + "averageResponseTime": 38025.8666666667, + "impact": 3.67248059487481, "key": Object { "service.name": "opbeans-python", "transaction.name": "opbeans.tasks.sync_orders", @@ -579,8 +579,8 @@ Array [ "transactionsPerMinute": 0.3, }, Object { - "averageResponseTime": 691636.3636363636, - "impact": 12.25042667907868, + "averageResponseTime": 691636.363636364, + "impact": 12.2504266790787, "key": Object { "service.name": "opbeans-rum", "transaction.name": "/customers", @@ -588,11 +588,11 @@ Array [ "serviceName": "opbeans-rum", "transactionName": "/customers", "transactionType": "page-load", - "transactionsPerMinute": 0.36666666666666664, + "transactionsPerMinute": 0.366666666666667, }, Object { "averageResponseTime": 1590910.5, - "impact": 20.494746747861388, + "impact": 20.4947467478614, "key": Object { "service.name": "opbeans-go", "transaction.name": "GET /api/orders", @@ -600,11 +600,11 @@ Array [ "serviceName": "opbeans-go", "transactionName": "GET /api/orders", "transactionType": "request", - "transactionsPerMinute": 0.26666666666666666, + "transactionsPerMinute": 0.266666666666667, }, Object { - "averageResponseTime": 303589.16279069765, - "impact": 21.02144244954455, + "averageResponseTime": 303589.162790698, + "impact": 21.0214424495446, "key": Object { "service.name": "opbeans-ruby", "transaction.name": "Rack", @@ -612,11 +612,11 @@ Array [ "serviceName": "opbeans-ruby", "transactionName": "Rack", "transactionType": "request", - "transactionsPerMinute": 1.4333333333333333, + "transactionsPerMinute": 1.43333333333333, }, Object { "averageResponseTime": 1180200, - "impact": 28.507858596190804, + "impact": 28.5078585961908, "key": Object { "service.name": "opbeans-rum", "transaction.name": "/products", @@ -627,8 +627,8 @@ Array [ "transactionsPerMinute": 0.5, }, Object { - "averageResponseTime": 1073178.5714285714, - "impact": 48.390399898683754, + "averageResponseTime": 1073178.57142857, + "impact": 48.3903998986838, "key": Object { "service.name": "opbeans-rum", "transaction.name": "/dashboard", @@ -636,11 +636,11 @@ Array [ "serviceName": "opbeans-rum", "transactionName": "/dashboard", "transactionType": "page-load", - "transactionsPerMinute": 0.9333333333333333, + "transactionsPerMinute": 0.933333333333333, }, Object { - "averageResponseTime": 2676214.285714286, - "impact": 60.33667329750868, + "averageResponseTime": 2676214.28571429, + "impact": 60.3366732975087, "key": Object { "service.name": "opbeans-rum", "transaction.name": "/orders", @@ -648,11 +648,11 @@ Array [ "serviceName": "opbeans-rum", "transactionName": "/orders", "transactionType": "page-load", - "transactionsPerMinute": 0.4666666666666667, + "transactionsPerMinute": 0.466666666666667, }, Object { - "averageResponseTime": 928922.4347826086, - "impact": 68.81313564424958, + "averageResponseTime": 928922.434782609, + "impact": 68.8131356442496, "key": Object { "service.name": "opbeans-node", "transaction.name": "Process completed order", @@ -660,11 +660,11 @@ Array [ "serviceName": "opbeans-node", "transactionName": "Process completed order", "transactionType": "Worker", - "transactionsPerMinute": 1.5333333333333334, + "transactionsPerMinute": 1.53333333333333, }, Object { - "averageResponseTime": 1012219.0930232558, - "impact": 70.09342088866295, + "averageResponseTime": 1012219.09302326, + "impact": 70.0934208886629, "key": Object { "service.name": "opbeans-node", "transaction.name": "Process payment", @@ -672,11 +672,11 @@ Array [ "serviceName": "opbeans-node", "transactionName": "Process payment", "transactionType": "Worker", - "transactionsPerMinute": 1.4333333333333333, + "transactionsPerMinute": 1.43333333333333, }, Object { - "averageResponseTime": 126010.60833333334, - "impact": 73.05405786950051, + "averageResponseTime": 126010.608333333, + "impact": 73.0540578695005, "key": Object { "service.name": "opbeans-python", "transaction.name": "opbeans.tasks.update_stats", @@ -687,8 +687,8 @@ Array [ "transactionsPerMinute": 12, }, Object { - "averageResponseTime": 1041680.2444444444, - "impact": 75.48871418577934, + "averageResponseTime": 1041680.24444444, + "impact": 75.4887141857793, "key": Object { "service.name": "opbeans-node", "transaction.name": "Update shipping status", diff --git a/x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts b/x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts index 3429301e4a326..b6fccf8f5b581 100644 --- a/x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts +++ b/x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts @@ -73,7 +73,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { "serviceName": "opbeans-node", "transactionName": "POST /api/orders", "transactionType": "request", - "transactionsPerMinute": 0.03333333333333333, + "transactionsPerMinute": 0.0333333333333333, } `); diff --git a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/__snapshots__/breakdown.snap b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/__snapshots__/breakdown.snap index 563bad8779e96..87938f6f1f122 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/__snapshots__/breakdown.snap +++ b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/__snapshots__/breakdown.snap @@ -36,7 +36,7 @@ Object { }, Object { "x": 1600160190000, - "y": 0.4827586206896552, + "y": 0.482758620689655, }, Object { "x": 1600160220000, @@ -52,7 +52,7 @@ Object { }, Object { "x": 1600160310000, - "y": 0.17142857142857143, + "y": 0.171428571428571, }, Object { "x": 1600160340000, @@ -68,15 +68,15 @@ Object { }, Object { "x": 1600160430000, - "y": 0.41964285714285715, + "y": 0.419642857142857, }, Object { "x": 1600160460000, - "y": 0.7222222222222222, + "y": 0.722222222222222, }, Object { "x": 1600160490000, - "y": 0.8333333333333334, + "y": 0.833333333333333, }, Object { "x": 1600160520000, @@ -88,7 +88,7 @@ Object { }, Object { "x": 1600160580000, - "y": 0.11044776119402985, + "y": 0.11044776119403, }, Object { "x": 1600160610000, @@ -100,15 +100,15 @@ Object { }, Object { "x": 1600160670000, - "y": 0.15028901734104047, + "y": 0.15028901734104, }, Object { "x": 1600160700000, - "y": 0.38095238095238093, + "y": 0.380952380952381, }, Object { "x": 1600160730000, - "y": 0.06761565836298933, + "y": 0.0676156583629893, }, Object { "x": 1600160760000, @@ -116,7 +116,7 @@ Object { }, Object { "x": 1600160790000, - "y": 0.26373626373626374, + "y": 0.263736263736264, }, Object { "x": 1600160820000, @@ -124,7 +124,7 @@ Object { }, Object { "x": 1600160850000, - "y": 0.5294117647058824, + "y": 0.529411764705882, }, Object { "x": 1600160880000, @@ -132,11 +132,11 @@ Object { }, Object { "x": 1600160910000, - "y": 0.012096774193548387, + "y": 0.0120967741935484, }, Object { "x": 1600160940000, - "y": 0.26126126126126126, + "y": 0.261261261261261, }, Object { "x": 1600160970000, @@ -148,11 +148,11 @@ Object { }, Object { "x": 1600161030000, - "y": 0.16071428571428573, + "y": 0.160714285714286, }, Object { "x": 1600161060000, - "y": 0.040268456375838924, + "y": 0.0402684563758389, }, Object { "x": 1600161090000, @@ -164,11 +164,11 @@ Object { }, Object { "x": 1600161150000, - "y": 0.07894736842105263, + "y": 0.0789473684210526, }, Object { "x": 1600161180000, - "y": 0.4074074074074074, + "y": 0.407407407407407, }, Object { "x": 1600161210000, @@ -180,11 +180,11 @@ Object { }, Object { "x": 1600161270000, - "y": 0.6666666666666666, + "y": 0.666666666666667, }, Object { "x": 1600161300000, - "y": 0.8214285714285714, + "y": 0.821428571428571, }, Object { "x": 1600161330000, @@ -196,11 +196,11 @@ Object { }, Object { "x": 1600161390000, - "y": 0.17333333333333334, + "y": 0.173333333333333, }, Object { "x": 1600161420000, - "y": 0.14285714285714285, + "y": 0.142857142857143, }, Object { "x": 1600161450000, @@ -212,7 +212,7 @@ Object { }, Object { "x": 1600161510000, - "y": 0.42105263157894735, + "y": 0.421052631578947, }, Object { "x": 1600161540000, @@ -232,7 +232,7 @@ Object { }, Object { "x": 1600161660000, - "y": 0.018518518518518517, + "y": 0.0185185185185185, }, Object { "x": 1600161690000, @@ -244,11 +244,11 @@ Object { }, Object { "x": 1600161750000, - "y": 0.36764705882352944, + "y": 0.367647058823529, }, Object { "x": 1600161780000, - "y": 0.10526315789473684, + "y": 0.105263157894737, }, ], "hideLegend": false, @@ -289,7 +289,7 @@ Object { }, Object { "x": 1600160190000, - "y": 0.41379310344827586, + "y": 0.413793103448276, }, Object { "x": 1600160220000, @@ -305,7 +305,7 @@ Object { }, Object { "x": 1600160310000, - "y": 0.6285714285714286, + "y": 0.628571428571429, }, Object { "x": 1600160340000, @@ -341,7 +341,7 @@ Object { }, Object { "x": 1600160580000, - "y": 0.8895522388059701, + "y": 0.88955223880597, }, Object { "x": 1600160610000, @@ -353,7 +353,7 @@ Object { }, Object { "x": 1600160670000, - "y": 0.7052023121387283, + "y": 0.705202312138728, }, Object { "x": 1600160700000, @@ -361,7 +361,7 @@ Object { }, Object { "x": 1600160730000, - "y": 0.8718861209964412, + "y": 0.871886120996441, }, Object { "x": 1600160760000, @@ -369,7 +369,7 @@ Object { }, Object { "x": 1600160790000, - "y": 0.6703296703296703, + "y": 0.67032967032967, }, Object { "x": 1600160820000, @@ -385,11 +385,11 @@ Object { }, Object { "x": 1600160910000, - "y": 0.9879032258064516, + "y": 0.987903225806452, }, Object { "x": 1600160940000, - "y": 0.7387387387387387, + "y": 0.738738738738739, }, Object { "x": 1600160970000, @@ -401,7 +401,7 @@ Object { }, Object { "x": 1600161030000, - "y": 0.7946428571428571, + "y": 0.794642857142857, }, Object { "x": 1600161060000, @@ -417,7 +417,7 @@ Object { }, Object { "x": 1600161150000, - "y": 0.9210526315789473, + "y": 0.921052631578947, }, Object { "x": 1600161180000, @@ -449,11 +449,11 @@ Object { }, Object { "x": 1600161390000, - "y": 0.7466666666666667, + "y": 0.746666666666667, }, Object { "x": 1600161420000, - "y": 0.8571428571428571, + "y": 0.857142857142857, }, Object { "x": 1600161450000, @@ -465,7 +465,7 @@ Object { }, Object { "x": 1600161510000, - "y": 0.5789473684210527, + "y": 0.578947368421053, }, Object { "x": 1600161540000, @@ -485,7 +485,7 @@ Object { }, Object { "x": 1600161660000, - "y": 0.9814814814814815, + "y": 0.981481481481482, }, Object { "x": 1600161690000, @@ -497,11 +497,11 @@ Object { }, Object { "x": 1600161750000, - "y": 0.5588235294117647, + "y": 0.558823529411765, }, Object { "x": 1600161780000, - "y": 0.8947368421052632, + "y": 0.894736842105263, }, ], "hideLegend": false, @@ -542,7 +542,7 @@ Object { }, Object { "x": 1600160190000, - "y": 0.10344827586206896, + "y": 0.103448275862069, }, Object { "x": 1600160220000, @@ -574,15 +574,15 @@ Object { }, Object { "x": 1600160430000, - "y": 0.14285714285714285, + "y": 0.142857142857143, }, Object { "x": 1600160460000, - "y": 0.2777777777777778, + "y": 0.277777777777778, }, Object { "x": 1600160490000, - "y": 0.16666666666666666, + "y": 0.166666666666667, }, Object { "x": 1600160520000, @@ -606,15 +606,15 @@ Object { }, Object { "x": 1600160670000, - "y": 0.14450867052023122, + "y": 0.144508670520231, }, Object { "x": 1600160700000, - "y": 0.6190476190476191, + "y": 0.619047619047619, }, Object { "x": 1600160730000, - "y": 0.060498220640569395, + "y": 0.0604982206405694, }, Object { "x": 1600160760000, @@ -622,7 +622,7 @@ Object { }, Object { "x": 1600160790000, - "y": 0.06593406593406594, + "y": 0.0659340659340659, }, Object { "x": 1600160820000, @@ -630,7 +630,7 @@ Object { }, Object { "x": 1600160850000, - "y": 0.47058823529411764, + "y": 0.470588235294118, }, Object { "x": 1600160880000, @@ -654,7 +654,7 @@ Object { }, Object { "x": 1600161030000, - "y": 0.044642857142857144, + "y": 0.0446428571428571, }, Object { "x": 1600161060000, @@ -674,7 +674,7 @@ Object { }, Object { "x": 1600161180000, - "y": 0.5925925925925926, + "y": 0.592592592592593, }, Object { "x": 1600161210000, @@ -686,11 +686,11 @@ Object { }, Object { "x": 1600161270000, - "y": 0.3333333333333333, + "y": 0.333333333333333, }, Object { "x": 1600161300000, - "y": 0.17857142857142858, + "y": 0.178571428571429, }, Object { "x": 1600161330000, @@ -750,7 +750,7 @@ Object { }, Object { "x": 1600161750000, - "y": 0.07352941176470588, + "y": 0.0735294117647059, }, Object { "x": 1600161780000, diff --git a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/__snapshots__/error_rate.snap b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/__snapshots__/error_rate.snap index f9ab0ed8ff8cf..ab228385aaf56 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/__snapshots__/error_rate.snap +++ b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/__snapshots__/error_rate.snap @@ -12,7 +12,7 @@ Array [ }, Object { "x": 1600160040000, - "y": 0.14285714285714285, + "y": 0.142857142857143, }, Object { "x": 1600160070000, @@ -44,11 +44,11 @@ Array [ }, Object { "x": 1600160280000, - "y": 0.16666666666666666, + "y": 0.166666666666667, }, Object { "x": 1600160310000, - "y": 0.3333333333333333, + "y": 0.333333333333333, }, Object { "x": 1600160340000, @@ -76,7 +76,7 @@ Array [ }, Object { "x": 1600160520000, - "y": 0.16666666666666666, + "y": 0.166666666666667, }, Object { "x": 1600160550000, diff --git a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/__snapshots__/top_transaction_groups.snap b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/__snapshots__/top_transaction_groups.snap index e37b2283f009a..93f22e67e1a02 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/__snapshots__/top_transaction_groups.snap +++ b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/__snapshots__/top_transaction_groups.snap @@ -10,41 +10,41 @@ Array [ "serviceName": "opbeans-node", "transactionName": "POST /api/orders", "transactionType": "request", - "transactionsPerMinute": 0.03333333333333333, + "transactionsPerMinute": 0.0333333333333333, }, Object { "averageResponseTime": 4479, - "impact": 0.1825278966745733, + "impact": 0.182527896674573, "key": "GET /api/customers/:id", "p95": 4448, "serviceName": "opbeans-node", "transactionName": "GET /api/customers/:id", "transactionType": "request", - "transactionsPerMinute": 0.03333333333333333, + "transactionsPerMinute": 0.0333333333333333, }, Object { "averageResponseTime": 2754.5, - "impact": 0.23878275411766442, + "impact": 0.238782754117664, "key": "GET /*", "p95": 2832, "serviceName": "opbeans-node", "transactionName": "GET /*", "transactionType": "request", - "transactionsPerMinute": 0.06666666666666667, + "transactionsPerMinute": 0.0666666666666667, }, Object { "averageResponseTime": 10841, - "impact": 1.122093248707094, + "impact": 1.12209324870709, "key": "GET /api/orders/:id", "p95": 13376, "serviceName": "opbeans-node", "transactionName": "GET /api/orders/:id", "transactionType": "request", - "transactionsPerMinute": 0.06666666666666667, + "transactionsPerMinute": 0.0666666666666667, }, Object { - "averageResponseTime": 10551.333333333334, - "impact": 1.6667276549425354, + "averageResponseTime": 10551.3333333333, + "impact": 1.66672765494254, "key": "GET /api/products/top", "p95": 19552, "serviceName": "opbeans-node", @@ -54,37 +54,37 @@ Array [ }, Object { "averageResponseTime": 15988, - "impact": 1.6843141249393074, + "impact": 1.68431412493931, "key": "GET /api/products/:id", "p95": 16000, "serviceName": "opbeans-node", "transactionName": "GET /api/products/:id", "transactionType": "request", - "transactionsPerMinute": 0.06666666666666667, + "transactionsPerMinute": 0.0666666666666667, }, Object { "averageResponseTime": 9499, - "impact": 2.013104650965918, + "impact": 2.01310465096592, "key": "GET /api/types", "p95": 14944, "serviceName": "opbeans-node", "transactionName": "GET /api/types", "transactionType": "request", - "transactionsPerMinute": 0.13333333333333333, + "transactionsPerMinute": 0.133333333333333, }, Object { "averageResponseTime": 12228, - "impact": 2.6092969071297842, + "impact": 2.60929690712978, "key": "GET /api/products/:id/customers", "p95": 17760, "serviceName": "opbeans-node", "transactionName": "GET /api/products/:id/customers", "transactionType": "request", - "transactionsPerMinute": 0.13333333333333333, + "transactionsPerMinute": 0.133333333333333, }, Object { - "averageResponseTime": 22555.666666666668, - "impact": 3.633626859892089, + "averageResponseTime": 22555.6666666667, + "impact": 3.63362685989209, "key": "GET /api/customers", "p95": 25984, "serviceName": "opbeans-node", @@ -94,17 +94,17 @@ Array [ }, Object { "averageResponseTime": 13852.6, - "impact": 3.7207945807456553, + "impact": 3.72079458074566, "key": "GET /api/types/:id", "p95": 21984, "serviceName": "opbeans-node", "transactionName": "GET /api/types/:id", "transactionType": "request", - "transactionsPerMinute": 0.16666666666666666, + "transactionsPerMinute": 0.166666666666667, }, Object { "averageResponseTime": 12228.5, - "impact": 3.9451586141206243, + "impact": 3.94515861412062, "key": "GET /api/orders", "p95": 16736, "serviceName": "opbeans-node", @@ -113,18 +113,18 @@ Array [ "transactionsPerMinute": 0.2, }, Object { - "averageResponseTime": 12491.42857142857, + "averageResponseTime": 12491.4285714286, "impact": 4.71355627370009, "key": "GET /api/products", "p95": 30448, "serviceName": "opbeans-node", "transactionName": "GET /api/products", "transactionType": "request", - "transactionsPerMinute": 0.23333333333333334, + "transactionsPerMinute": 0.233333333333333, }, Object { - "averageResponseTime": 23683.333333333332, - "impact": 11.579379700079686, + "averageResponseTime": 23683.3333333333, + "impact": 11.5793797000797, "key": "GET /api/stats", "p95": 36288, "serviceName": "opbeans-node", @@ -133,14 +133,14 @@ Array [ "transactionsPerMinute": 0.3, }, Object { - "averageResponseTime": 42606.74418604651, + "averageResponseTime": 42606.7441860465, "impact": 100, "key": "GET /api", "p95": 131008, "serviceName": "opbeans-node", "transactionName": "GET /api", "transactionType": "request", - "transactionsPerMinute": 1.4333333333333333, + "transactionsPerMinute": 1.43333333333333, }, ] `; diff --git a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/__snapshots__/transaction_charts.snap b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/__snapshots__/transaction_charts.snap index aaeac9edf01b8..9ed103b445575 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/__snapshots__/transaction_charts.snap +++ b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/__snapshots__/transaction_charts.snap @@ -3,7 +3,7 @@ exports[`Transaction charts when data is loaded returns the correct data 4`] = ` Object { "apmTimeseries": Object { - "overallAvgDuration": 600255.7079646018, + "overallAvgDuration": 600255.707964602, "responseTimes": Object { "avg": Array [ Object { @@ -32,11 +32,11 @@ Object { }, Object { "x": 1600160160000, - "y": 467003.6666666667, + "y": 467003.666666667, }, Object { "x": 1600160190000, - "y": 863809.6666666666, + "y": 863809.666666667, }, Object { "x": 1600160220000, @@ -64,7 +64,7 @@ Object { }, Object { "x": 1600160400000, - "y": 368087.9090909091, + "y": 368087.909090909, }, Object { "x": 1600160430000, @@ -92,11 +92,11 @@ Object { }, Object { "x": 1600160610000, - "y": 882789.6666666666, + "y": 882789.666666667, }, Object { "x": 1600160640000, - "y": 238075.9090909091, + "y": 238075.909090909, }, Object { "x": 1600160670000, @@ -112,11 +112,11 @@ Object { }, Object { "x": 1600160760000, - "y": 282337.1666666667, + "y": 282337.166666667, }, Object { "x": 1600160790000, - "y": 987012.3333333334, + "y": 987012.333333333, }, Object { "x": 1600160820000, @@ -136,7 +136,7 @@ Object { }, Object { "x": 1600160940000, - "y": 1313632.6666666667, + "y": 1313632.66666667, }, Object { "x": 1600160970000, @@ -144,11 +144,11 @@ Object { }, Object { "x": 1600161000000, - "y": 611899.1428571428, + "y": 611899.142857143, }, Object { "x": 1600161030000, - "y": 273321.85714285716, + "y": 273321.857142857, }, Object { "x": 1600161060000, @@ -156,7 +156,7 @@ Object { }, Object { "x": 1600161090000, - "y": 1446104.6666666667, + "y": 1446104.66666667, }, Object { "x": 1600161120000, @@ -172,11 +172,11 @@ Object { }, Object { "x": 1600161210000, - "y": 1054428.6666666667, + "y": 1054428.66666667, }, Object { "x": 1600161240000, - "y": 816781.3333333334, + "y": 816781.333333333, }, Object { "x": 1600161270000, @@ -192,7 +192,7 @@ Object { }, Object { "x": 1600161360000, - "y": 714202.3333333334, + "y": 714202.333333333, }, Object { "x": 1600161390000, @@ -204,7 +204,7 @@ Object { }, Object { "x": 1600161450000, - "y": 836182.3333333334, + "y": 836182.333333333, }, Object { "x": 1600161480000, @@ -212,11 +212,11 @@ Object { }, Object { "x": 1600161510000, - "y": 615193.3333333334, + "y": 615193.333333333, }, Object { "x": 1600161540000, - "y": 946298.6666666666, + "y": 946298.666666667, }, Object { "x": 1600161570000, @@ -240,7 +240,7 @@ Object { }, Object { "x": 1600161720000, - "y": 450557.77777777775, + "y": 450557.777777778, }, Object { "x": 1600161750000, @@ -746,7 +746,7 @@ Object { }, "tpmBuckets": Array [ Object { - "avg": 2.8333333333333335, + "avg": 2.83333333333333, "dataPoints": Array [ Object { "x": 1600159980000, @@ -996,7 +996,7 @@ Object { "key": "HTTP 2xx", }, Object { - "avg": 0.23333333333333334, + "avg": 0.233333333333333, "dataPoints": Array [ Object { "x": 1600159980000, @@ -1246,7 +1246,7 @@ Object { "key": "HTTP 4xx", }, Object { - "avg": 4.466666666666667, + "avg": 4.46666666666667, "dataPoints": Array [ Object { "x": 1600159980000, diff --git a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/error_rate.ts b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/error_rate.ts index 27a2eac3131f5..17ada95ca4958 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/error_rate.ts +++ b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/error_rate.ts @@ -80,7 +80,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('has the correct calculation for average', () => { - expectSnapshot(errorRateResponse.average).toMatchInline(`0.14086309523809523`); + expectSnapshot(errorRateResponse.average).toMatchInline(`0.140863095238095`); }); it('has the correct error rate', () => { diff --git a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/transaction_charts.ts b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/transaction_charts.ts index 8dd52ef241c59..ef874695e6046 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/transaction_charts.ts +++ b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/transaction_charts.ts @@ -62,7 +62,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns the correct data', () => { expectSnapshot(response.body.apmTimeseries.overallAvgDuration).toMatchInline( - `600255.7079646018` + `600255.707964602` ); expectSnapshot(response.body.apmTimeseries.responseTimes.avg.length).toMatchInline(`61`); expectSnapshot(response.body.apmTimeseries.tpmBuckets.length).toMatchInline(`3`); diff --git a/x-pack/test/apm_api_integration/common/match_snapshot.ts b/x-pack/test/apm_api_integration/common/match_snapshot.ts index 4ac812a0ee168..d260a19b60df4 100644 --- a/x-pack/test/apm_api_integration/common/match_snapshot.ts +++ b/x-pack/test/apm_api_integration/common/match_snapshot.ts @@ -4,7 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SnapshotState, toMatchSnapshot, toMatchInlineSnapshot } from 'jest-snapshot'; +import { + SnapshotState, + toMatchSnapshot, + toMatchInlineSnapshot, + addSerializer, +} from 'jest-snapshot'; import path from 'path'; import expect from '@kbn/expect'; // @ts-expect-error @@ -62,6 +67,15 @@ export function registerMochaHooksForSnapshots() { { snapshotState: ISnapshotState; testsInFile: Test[] } > = {}; + addSerializer({ + serialize: (num: number) => { + return String(parseFloat(num.toPrecision(15))); + }, + test: (value: any) => { + return typeof value === 'number'; + }, + }); + registered = true; beforeEach(function () { diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_views.snap b/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_views.snap new file mode 100644 index 0000000000000..38b009fc73d34 --- /dev/null +++ b/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_views.snap @@ -0,0 +1,280 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CSM page views when there is data returns page views 1`] = ` +Object { + "items": Array [ + Object { + "x": 1600149947000, + "y": 1, + }, + Object { + "x": 1600149957000, + "y": 0, + }, + Object { + "x": 1600149967000, + "y": 0, + }, + Object { + "x": 1600149977000, + "y": 0, + }, + Object { + "x": 1600149987000, + "y": 0, + }, + Object { + "x": 1600149997000, + "y": 0, + }, + Object { + "x": 1600150007000, + "y": 0, + }, + Object { + "x": 1600150017000, + "y": 0, + }, + Object { + "x": 1600150027000, + "y": 1, + }, + Object { + "x": 1600150037000, + "y": 0, + }, + Object { + "x": 1600150047000, + "y": 0, + }, + Object { + "x": 1600150057000, + "y": 0, + }, + Object { + "x": 1600150067000, + "y": 0, + }, + Object { + "x": 1600150077000, + "y": 1, + }, + Object { + "x": 1600150087000, + "y": 0, + }, + Object { + "x": 1600150097000, + "y": 0, + }, + Object { + "x": 1600150107000, + "y": 0, + }, + Object { + "x": 1600150117000, + "y": 0, + }, + Object { + "x": 1600150127000, + "y": 0, + }, + Object { + "x": 1600150137000, + "y": 0, + }, + Object { + "x": 1600150147000, + "y": 0, + }, + Object { + "x": 1600150157000, + "y": 0, + }, + Object { + "x": 1600150167000, + "y": 0, + }, + Object { + "x": 1600150177000, + "y": 1, + }, + Object { + "x": 1600150187000, + "y": 0, + }, + Object { + "x": 1600150197000, + "y": 0, + }, + Object { + "x": 1600150207000, + "y": 1, + }, + Object { + "x": 1600150217000, + "y": 0, + }, + Object { + "x": 1600150227000, + "y": 0, + }, + Object { + "x": 1600150237000, + "y": 1, + }, + ], + "topItems": Array [], +} +`; + +exports[`CSM page views when there is data returns page views with breakdown 1`] = ` +Object { + "items": Array [ + Object { + "Chrome": 1, + "x": 1600149947000, + "y": 1, + }, + Object { + "x": 1600149957000, + "y": 0, + }, + Object { + "x": 1600149967000, + "y": 0, + }, + Object { + "x": 1600149977000, + "y": 0, + }, + Object { + "x": 1600149987000, + "y": 0, + }, + Object { + "x": 1600149997000, + "y": 0, + }, + Object { + "x": 1600150007000, + "y": 0, + }, + Object { + "x": 1600150017000, + "y": 0, + }, + Object { + "Chrome": 1, + "x": 1600150027000, + "y": 1, + }, + Object { + "x": 1600150037000, + "y": 0, + }, + Object { + "x": 1600150047000, + "y": 0, + }, + Object { + "x": 1600150057000, + "y": 0, + }, + Object { + "x": 1600150067000, + "y": 0, + }, + Object { + "Chrome": 1, + "x": 1600150077000, + "y": 1, + }, + Object { + "x": 1600150087000, + "y": 0, + }, + Object { + "x": 1600150097000, + "y": 0, + }, + Object { + "x": 1600150107000, + "y": 0, + }, + Object { + "x": 1600150117000, + "y": 0, + }, + Object { + "x": 1600150127000, + "y": 0, + }, + Object { + "x": 1600150137000, + "y": 0, + }, + Object { + "x": 1600150147000, + "y": 0, + }, + Object { + "x": 1600150157000, + "y": 0, + }, + Object { + "x": 1600150167000, + "y": 0, + }, + Object { + "Chrome": 1, + "x": 1600150177000, + "y": 1, + }, + Object { + "x": 1600150187000, + "y": 0, + }, + Object { + "x": 1600150197000, + "y": 0, + }, + Object { + "Chrome Mobile": 1, + "x": 1600150207000, + "y": 1, + }, + Object { + "x": 1600150217000, + "y": 0, + }, + Object { + "x": 1600150227000, + "y": 0, + }, + Object { + "Chrome Mobile": 1, + "x": 1600150237000, + "y": 1, + }, + ], + "topItems": Array [ + "Chrome", + "Chrome Mobile", + ], +} +`; + +exports[`CSM page views when there is no data returns empty list 1`] = ` +Object { + "items": Array [], + "topItems": Array [], +} +`; + +exports[`CSM page views when there is no data returns empty list with breakdowns 1`] = ` +Object { + "items": Array [], + "topItems": Array [], +} +`; diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts b/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts new file mode 100644 index 0000000000000..ca5670d41d8ee --- /dev/null +++ b/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { expectSnapshot } from '../../../common/match_snapshot'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +export default function rumServicesApiTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('CSM page views', () => { + describe('when there is no data', () => { + it('returns empty list', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' + ); + + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatch(); + }); + it('returns empty list with breakdowns', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&breakdowns=%7B%22name%22%3A%22Browser%22%2C%22fieldName%22%3A%22user_agent.name%22%2C%22type%22%3A%22category%22%7D' + ); + + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatch(); + }); + }); + + describe('when there is data', () => { + before(async () => { + await esArchiver.load('8.0.0'); + await esArchiver.load('rum_8.0.0'); + }); + after(async () => { + await esArchiver.unload('8.0.0'); + await esArchiver.unload('rum_8.0.0'); + }); + + it('returns page views', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatch(); + }); + it('returns page views with breakdown', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&breakdowns=%7B%22name%22%3A%22Browser%22%2C%22fieldName%22%3A%22user_agent.name%22%2C%22type%22%3A%22category%22%7D' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatch(); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/url_search.ts b/x-pack/test/apm_api_integration/trial/tests/csm/url_search.ts new file mode 100644 index 0000000000000..76dc758895e32 --- /dev/null +++ b/x-pack/test/apm_api_integration/trial/tests/csm/url_search.ts @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { expectSnapshot } from '../../../common/match_snapshot'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +export default function rumServicesApiTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('CSM url search api', () => { + describe('when there is no data', () => { + it('returns empty list', async () => { + const response = await supertest.get( + '/api/apm/rum-client/url-search?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' + ); + + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatchInline(` + Object { + "items": Array [], + "total": 0, + } + `); + }); + }); + + describe('when there is data', () => { + before(async () => { + await esArchiver.load('8.0.0'); + await esArchiver.load('rum_8.0.0'); + }); + after(async () => { + await esArchiver.unload('8.0.0'); + await esArchiver.unload('rum_8.0.0'); + }); + + it('returns top urls when no query', async () => { + const response = await supertest.get( + '/api/apm/rum-client/url-search?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatchInline(` + Object { + "items": Array [ + Object { + "count": 5, + "pld": 4924000, + "url": "http://localhost:5601/nfw/app/csm?rangeFrom=now-15m&rangeTo=now&serviceName=kibana-frontend-8_0_0", + }, + Object { + "count": 1, + "pld": 2760000, + "url": "http://localhost:5601/nfw/app/home", + }, + ], + "total": 2, + } + `); + }); + + it('returns specific results against query', async () => { + const response = await supertest.get( + '/api/apm/rum-client/url-search?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&urlQuery=csm' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatchInline(` + Object { + "items": Array [ + Object { + "count": 5, + "pld": 4924000, + "url": "http://localhost:5601/nfw/app/csm?rangeFrom=now-15m&rangeTo=now&serviceName=kibana-frontend-8_0_0", + }, + ], + "total": 1, + } + `); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/trial/tests/index.ts b/x-pack/test/apm_api_integration/trial/tests/index.ts index ae62253c62d81..69e54ea33c559 100644 --- a/x-pack/test/apm_api_integration/trial/tests/index.ts +++ b/x-pack/test/apm_api_integration/trial/tests/index.ts @@ -35,6 +35,8 @@ export default function observabilityApiIntegrationTests({ loadTestFile }: FtrPr loadTestFile(require.resolve('./csm/csm_services.ts')); loadTestFile(require.resolve('./csm/web_core_vitals.ts')); loadTestFile(require.resolve('./csm/long_task_metrics.ts')); + loadTestFile(require.resolve('./csm/url_search.ts')); + loadTestFile(require.resolve('./csm/page_views.ts')); }); }); } diff --git a/x-pack/test/apm_api_integration/trial/tests/service_maps/__snapshots__/service_maps.snap b/x-pack/test/apm_api_integration/trial/tests/service_maps/__snapshots__/service_maps.snap index bf42c08438156..8a3929f1e9ba6 100644 --- a/x-pack/test/apm_api_integration/trial/tests/service_maps/__snapshots__/service_maps.snap +++ b/x-pack/test/apm_api_integration/trial/tests/service_maps/__snapshots__/service_maps.snap @@ -75,8 +75,8 @@ Array [ "service.environment": "testing", "service.name": "opbeans-go", "serviceAnomalyStats": Object { - "actualValue": 3933482.1764705875, - "anomalyScore": 2.6101702751482714, + "actualValue": 3933482.17647059, + "anomalyScore": 2.61017027514827, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", @@ -103,8 +103,8 @@ Array [ "service.environment": "testing", "service.name": "opbeans-go", "serviceAnomalyStats": Object { - "actualValue": 3933482.1764705875, - "anomalyScore": 2.6101702751482714, + "actualValue": 3933482.17647059, + "anomalyScore": 2.61017027514827, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", @@ -117,7 +117,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-java", "serviceAnomalyStats": Object { - "actualValue": 14901.319999999996, + "actualValue": 14901.32, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -137,8 +137,8 @@ Array [ "service.environment": "testing", "service.name": "opbeans-go", "serviceAnomalyStats": Object { - "actualValue": 3933482.1764705875, - "anomalyScore": 2.6101702751482714, + "actualValue": 3933482.17647059, + "anomalyScore": 2.61017027514827, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", @@ -151,7 +151,7 @@ Array [ "service.environment": "testing", "service.name": "opbeans-node", "serviceAnomalyStats": Object { - "actualValue": 32226.649122807008, + "actualValue": 32226.649122807, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", @@ -171,8 +171,8 @@ Array [ "service.environment": "testing", "service.name": "opbeans-go", "serviceAnomalyStats": Object { - "actualValue": 3933482.1764705875, - "anomalyScore": 2.6101702751482714, + "actualValue": 3933482.17647059, + "anomalyScore": 2.61017027514827, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", @@ -185,7 +185,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-python", "serviceAnomalyStats": Object { - "actualValue": 66218.08333333333, + "actualValue": 66218.0833333333, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -204,7 +204,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-java", "serviceAnomalyStats": Object { - "actualValue": 14901.319999999996, + "actualValue": 14901.32, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -232,7 +232,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-java", "serviceAnomalyStats": Object { - "actualValue": 14901.319999999996, + "actualValue": 14901.32, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -246,8 +246,8 @@ Array [ "service.environment": "testing", "service.name": "opbeans-go", "serviceAnomalyStats": Object { - "actualValue": 3933482.1764705875, - "anomalyScore": 2.6101702751482714, + "actualValue": 3933482.17647059, + "anomalyScore": 2.61017027514827, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", @@ -266,7 +266,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-java", "serviceAnomalyStats": Object { - "actualValue": 14901.319999999996, + "actualValue": 14901.32, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -280,7 +280,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-python", "serviceAnomalyStats": Object { - "actualValue": 66218.08333333333, + "actualValue": 66218.0833333333, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -300,7 +300,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-java", "serviceAnomalyStats": Object { - "actualValue": 14901.319999999996, + "actualValue": 14901.32, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -314,8 +314,8 @@ Array [ "service.environment": "production", "service.name": "opbeans-ruby", "serviceAnomalyStats": Object { - "actualValue": 684716.5813953485, - "anomalyScore": 0.20498907719907372, + "actualValue": 684716.581395349, + "anomalyScore": 0.204989077199074, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", @@ -333,7 +333,7 @@ Array [ "service.environment": "testing", "service.name": "opbeans-node", "serviceAnomalyStats": Object { - "actualValue": 32226.649122807008, + "actualValue": 32226.649122807, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", @@ -361,7 +361,7 @@ Array [ "service.environment": "testing", "service.name": "opbeans-node", "serviceAnomalyStats": Object { - "actualValue": 32226.649122807008, + "actualValue": 32226.649122807, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", @@ -375,8 +375,8 @@ Array [ "service.environment": "testing", "service.name": "opbeans-go", "serviceAnomalyStats": Object { - "actualValue": 3933482.1764705875, - "anomalyScore": 2.6101702751482714, + "actualValue": 3933482.17647059, + "anomalyScore": 2.61017027514827, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", @@ -395,7 +395,7 @@ Array [ "service.environment": "testing", "service.name": "opbeans-node", "serviceAnomalyStats": Object { - "actualValue": 32226.649122807008, + "actualValue": 32226.649122807, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", @@ -409,7 +409,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-python", "serviceAnomalyStats": Object { - "actualValue": 66218.08333333333, + "actualValue": 66218.0833333333, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -428,7 +428,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-python", "serviceAnomalyStats": Object { - "actualValue": 66218.08333333333, + "actualValue": 66218.0833333333, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -455,7 +455,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-python", "serviceAnomalyStats": Object { - "actualValue": 66218.08333333333, + "actualValue": 66218.0833333333, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -482,7 +482,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-python", "serviceAnomalyStats": Object { - "actualValue": 66218.08333333333, + "actualValue": 66218.0833333333, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -510,7 +510,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-python", "serviceAnomalyStats": Object { - "actualValue": 66218.08333333333, + "actualValue": 66218.0833333333, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -524,8 +524,8 @@ Array [ "service.environment": "testing", "service.name": "opbeans-go", "serviceAnomalyStats": Object { - "actualValue": 3933482.1764705875, - "anomalyScore": 2.6101702751482714, + "actualValue": 3933482.17647059, + "anomalyScore": 2.61017027514827, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", @@ -544,7 +544,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-python", "serviceAnomalyStats": Object { - "actualValue": 66218.08333333333, + "actualValue": 66218.0833333333, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -558,7 +558,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-java", "serviceAnomalyStats": Object { - "actualValue": 14901.319999999996, + "actualValue": 14901.32, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -578,7 +578,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-python", "serviceAnomalyStats": Object { - "actualValue": 66218.08333333333, + "actualValue": 66218.0833333333, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -592,7 +592,7 @@ Array [ "service.environment": "testing", "service.name": "opbeans-node", "serviceAnomalyStats": Object { - "actualValue": 32226.649122807008, + "actualValue": 32226.649122807, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", @@ -611,8 +611,8 @@ Array [ "service.environment": "production", "service.name": "opbeans-ruby", "serviceAnomalyStats": Object { - "actualValue": 684716.5813953485, - "anomalyScore": 0.20498907719907372, + "actualValue": 684716.581395349, + "anomalyScore": 0.204989077199074, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", @@ -638,8 +638,8 @@ Array [ "service.environment": "production", "service.name": "opbeans-ruby", "serviceAnomalyStats": Object { - "actualValue": 684716.5813953485, - "anomalyScore": 0.20498907719907372, + "actualValue": 684716.581395349, + "anomalyScore": 0.204989077199074, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", @@ -652,8 +652,8 @@ Array [ "service.environment": "testing", "service.name": "opbeans-go", "serviceAnomalyStats": Object { - "actualValue": 3933482.1764705875, - "anomalyScore": 2.6101702751482714, + "actualValue": 3933482.17647059, + "anomalyScore": 2.61017027514827, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", @@ -672,8 +672,8 @@ Array [ "service.environment": "production", "service.name": "opbeans-ruby", "serviceAnomalyStats": Object { - "actualValue": 684716.5813953485, - "anomalyScore": 0.20498907719907372, + "actualValue": 684716.581395349, + "anomalyScore": 0.204989077199074, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", @@ -686,7 +686,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-java", "serviceAnomalyStats": Object { - "actualValue": 14901.319999999996, + "actualValue": 14901.32, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -705,8 +705,8 @@ Array [ "service.environment": "production", "service.name": "opbeans-ruby", "serviceAnomalyStats": Object { - "actualValue": 684716.5813953485, - "anomalyScore": 0.20498907719907372, + "actualValue": 684716.581395349, + "anomalyScore": 0.204989077199074, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", @@ -719,7 +719,7 @@ Array [ "service.environment": "testing", "service.name": "opbeans-node", "serviceAnomalyStats": Object { - "actualValue": 32226.649122807008, + "actualValue": 32226.649122807, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", @@ -738,8 +738,8 @@ Array [ "service.environment": "production", "service.name": "opbeans-ruby", "serviceAnomalyStats": Object { - "actualValue": 684716.5813953485, - "anomalyScore": 0.20498907719907372, + "actualValue": 684716.581395349, + "anomalyScore": 0.204989077199074, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", @@ -752,7 +752,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-python", "serviceAnomalyStats": Object { - "actualValue": 66218.08333333333, + "actualValue": 66218.0833333333, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -784,8 +784,8 @@ Array [ "service.environment": "testing", "service.name": "opbeans-go", "serviceAnomalyStats": Object { - "actualValue": 3933482.1764705875, - "anomalyScore": 2.6101702751482714, + "actualValue": 3933482.17647059, + "anomalyScore": 2.61017027514827, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", @@ -816,7 +816,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-java", "serviceAnomalyStats": Object { - "actualValue": 14901.319999999996, + "actualValue": 14901.32, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -848,7 +848,7 @@ Array [ "service.environment": "testing", "service.name": "opbeans-node", "serviceAnomalyStats": Object { - "actualValue": 32226.649122807008, + "actualValue": 32226.649122807, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", @@ -880,7 +880,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-python", "serviceAnomalyStats": Object { - "actualValue": 66218.08333333333, + "actualValue": 66218.0833333333, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -912,8 +912,8 @@ Array [ "service.environment": "production", "service.name": "opbeans-ruby", "serviceAnomalyStats": Object { - "actualValue": 684716.5813953485, - "anomalyScore": 0.20498907719907372, + "actualValue": 684716.581395349, + "anomalyScore": 0.204989077199074, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", @@ -942,7 +942,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-python", "serviceAnomalyStats": Object { - "actualValue": 66218.08333333333, + "actualValue": 66218.0833333333, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -957,7 +957,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-java", "serviceAnomalyStats": Object { - "actualValue": 14901.319999999996, + "actualValue": 14901.32, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -972,7 +972,7 @@ Array [ "service.environment": "testing", "service.name": "opbeans-node", "serviceAnomalyStats": Object { - "actualValue": 32226.649122807008, + "actualValue": 32226.649122807, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", @@ -987,8 +987,8 @@ Array [ "service.environment": "testing", "service.name": "opbeans-go", "serviceAnomalyStats": Object { - "actualValue": 3933482.1764705875, - "anomalyScore": 2.6101702751482714, + "actualValue": 3933482.17647059, + "anomalyScore": 2.61017027514827, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", @@ -1002,8 +1002,8 @@ Array [ "service.environment": "production", "service.name": "opbeans-ruby", "serviceAnomalyStats": Object { - "actualValue": 684716.5813953485, - "anomalyScore": 0.20498907719907372, + "actualValue": 684716.581395349, + "anomalyScore": 0.204989077199074, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", @@ -1061,8 +1061,8 @@ Object { "service.environment": "testing", "service.name": "opbeans-go", "serviceAnomalyStats": Object { - "actualValue": 3933482.1764705875, - "anomalyScore": 2.6101702751482714, + "actualValue": 3933482.17647059, + "anomalyScore": 2.61017027514827, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", @@ -1089,8 +1089,8 @@ Object { "service.environment": "testing", "service.name": "opbeans-go", "serviceAnomalyStats": Object { - "actualValue": 3933482.1764705875, - "anomalyScore": 2.6101702751482714, + "actualValue": 3933482.17647059, + "anomalyScore": 2.61017027514827, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", @@ -1103,7 +1103,7 @@ Object { "service.environment": "production", "service.name": "opbeans-java", "serviceAnomalyStats": Object { - "actualValue": 14901.319999999996, + "actualValue": 14901.32, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -1123,8 +1123,8 @@ Object { "service.environment": "testing", "service.name": "opbeans-go", "serviceAnomalyStats": Object { - "actualValue": 3933482.1764705875, - "anomalyScore": 2.6101702751482714, + "actualValue": 3933482.17647059, + "anomalyScore": 2.61017027514827, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", @@ -1137,7 +1137,7 @@ Object { "service.environment": "testing", "service.name": "opbeans-node", "serviceAnomalyStats": Object { - "actualValue": 32226.649122807008, + "actualValue": 32226.649122807, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", @@ -1157,8 +1157,8 @@ Object { "service.environment": "testing", "service.name": "opbeans-go", "serviceAnomalyStats": Object { - "actualValue": 3933482.1764705875, - "anomalyScore": 2.6101702751482714, + "actualValue": 3933482.17647059, + "anomalyScore": 2.61017027514827, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", @@ -1171,7 +1171,7 @@ Object { "service.environment": "production", "service.name": "opbeans-python", "serviceAnomalyStats": Object { - "actualValue": 66218.08333333333, + "actualValue": 66218.0833333333, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -1190,7 +1190,7 @@ Object { "service.environment": "production", "service.name": "opbeans-java", "serviceAnomalyStats": Object { - "actualValue": 14901.319999999996, + "actualValue": 14901.32, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -1218,7 +1218,7 @@ Object { "service.environment": "production", "service.name": "opbeans-java", "serviceAnomalyStats": Object { - "actualValue": 14901.319999999996, + "actualValue": 14901.32, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -1232,8 +1232,8 @@ Object { "service.environment": "testing", "service.name": "opbeans-go", "serviceAnomalyStats": Object { - "actualValue": 3933482.1764705875, - "anomalyScore": 2.6101702751482714, + "actualValue": 3933482.17647059, + "anomalyScore": 2.61017027514827, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", @@ -1252,7 +1252,7 @@ Object { "service.environment": "production", "service.name": "opbeans-java", "serviceAnomalyStats": Object { - "actualValue": 14901.319999999996, + "actualValue": 14901.32, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -1266,7 +1266,7 @@ Object { "service.environment": "production", "service.name": "opbeans-python", "serviceAnomalyStats": Object { - "actualValue": 66218.08333333333, + "actualValue": 66218.0833333333, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -1286,7 +1286,7 @@ Object { "service.environment": "production", "service.name": "opbeans-java", "serviceAnomalyStats": Object { - "actualValue": 14901.319999999996, + "actualValue": 14901.32, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -1300,8 +1300,8 @@ Object { "service.environment": "production", "service.name": "opbeans-ruby", "serviceAnomalyStats": Object { - "actualValue": 684716.5813953485, - "anomalyScore": 0.20498907719907372, + "actualValue": 684716.581395349, + "anomalyScore": 0.204989077199074, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", @@ -1319,7 +1319,7 @@ Object { "service.environment": "testing", "service.name": "opbeans-node", "serviceAnomalyStats": Object { - "actualValue": 32226.649122807008, + "actualValue": 32226.649122807, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", @@ -1347,7 +1347,7 @@ Object { "service.environment": "testing", "service.name": "opbeans-node", "serviceAnomalyStats": Object { - "actualValue": 32226.649122807008, + "actualValue": 32226.649122807, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", @@ -1361,8 +1361,8 @@ Object { "service.environment": "testing", "service.name": "opbeans-go", "serviceAnomalyStats": Object { - "actualValue": 3933482.1764705875, - "anomalyScore": 2.6101702751482714, + "actualValue": 3933482.17647059, + "anomalyScore": 2.61017027514827, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", @@ -1381,7 +1381,7 @@ Object { "service.environment": "testing", "service.name": "opbeans-node", "serviceAnomalyStats": Object { - "actualValue": 32226.649122807008, + "actualValue": 32226.649122807, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", @@ -1395,7 +1395,7 @@ Object { "service.environment": "production", "service.name": "opbeans-python", "serviceAnomalyStats": Object { - "actualValue": 66218.08333333333, + "actualValue": 66218.0833333333, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -1414,7 +1414,7 @@ Object { "service.environment": "production", "service.name": "opbeans-python", "serviceAnomalyStats": Object { - "actualValue": 66218.08333333333, + "actualValue": 66218.0833333333, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -1441,7 +1441,7 @@ Object { "service.environment": "production", "service.name": "opbeans-python", "serviceAnomalyStats": Object { - "actualValue": 66218.08333333333, + "actualValue": 66218.0833333333, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -1468,7 +1468,7 @@ Object { "service.environment": "production", "service.name": "opbeans-python", "serviceAnomalyStats": Object { - "actualValue": 66218.08333333333, + "actualValue": 66218.0833333333, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -1496,7 +1496,7 @@ Object { "service.environment": "production", "service.name": "opbeans-python", "serviceAnomalyStats": Object { - "actualValue": 66218.08333333333, + "actualValue": 66218.0833333333, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -1510,8 +1510,8 @@ Object { "service.environment": "testing", "service.name": "opbeans-go", "serviceAnomalyStats": Object { - "actualValue": 3933482.1764705875, - "anomalyScore": 2.6101702751482714, + "actualValue": 3933482.17647059, + "anomalyScore": 2.61017027514827, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", @@ -1530,7 +1530,7 @@ Object { "service.environment": "production", "service.name": "opbeans-python", "serviceAnomalyStats": Object { - "actualValue": 66218.08333333333, + "actualValue": 66218.0833333333, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -1544,7 +1544,7 @@ Object { "service.environment": "production", "service.name": "opbeans-java", "serviceAnomalyStats": Object { - "actualValue": 14901.319999999996, + "actualValue": 14901.32, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -1564,7 +1564,7 @@ Object { "service.environment": "production", "service.name": "opbeans-python", "serviceAnomalyStats": Object { - "actualValue": 66218.08333333333, + "actualValue": 66218.0833333333, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -1578,7 +1578,7 @@ Object { "service.environment": "testing", "service.name": "opbeans-node", "serviceAnomalyStats": Object { - "actualValue": 32226.649122807008, + "actualValue": 32226.649122807, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", @@ -1597,8 +1597,8 @@ Object { "service.environment": "production", "service.name": "opbeans-ruby", "serviceAnomalyStats": Object { - "actualValue": 684716.5813953485, - "anomalyScore": 0.20498907719907372, + "actualValue": 684716.581395349, + "anomalyScore": 0.204989077199074, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", @@ -1624,8 +1624,8 @@ Object { "service.environment": "production", "service.name": "opbeans-ruby", "serviceAnomalyStats": Object { - "actualValue": 684716.5813953485, - "anomalyScore": 0.20498907719907372, + "actualValue": 684716.581395349, + "anomalyScore": 0.204989077199074, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", @@ -1638,8 +1638,8 @@ Object { "service.environment": "testing", "service.name": "opbeans-go", "serviceAnomalyStats": Object { - "actualValue": 3933482.1764705875, - "anomalyScore": 2.6101702751482714, + "actualValue": 3933482.17647059, + "anomalyScore": 2.61017027514827, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", @@ -1658,8 +1658,8 @@ Object { "service.environment": "production", "service.name": "opbeans-ruby", "serviceAnomalyStats": Object { - "actualValue": 684716.5813953485, - "anomalyScore": 0.20498907719907372, + "actualValue": 684716.581395349, + "anomalyScore": 0.204989077199074, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", @@ -1672,7 +1672,7 @@ Object { "service.environment": "production", "service.name": "opbeans-java", "serviceAnomalyStats": Object { - "actualValue": 14901.319999999996, + "actualValue": 14901.32, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -1691,8 +1691,8 @@ Object { "service.environment": "production", "service.name": "opbeans-ruby", "serviceAnomalyStats": Object { - "actualValue": 684716.5813953485, - "anomalyScore": 0.20498907719907372, + "actualValue": 684716.581395349, + "anomalyScore": 0.204989077199074, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", @@ -1705,7 +1705,7 @@ Object { "service.environment": "testing", "service.name": "opbeans-node", "serviceAnomalyStats": Object { - "actualValue": 32226.649122807008, + "actualValue": 32226.649122807, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", @@ -1724,8 +1724,8 @@ Object { "service.environment": "production", "service.name": "opbeans-ruby", "serviceAnomalyStats": Object { - "actualValue": 684716.5813953485, - "anomalyScore": 0.20498907719907372, + "actualValue": 684716.581395349, + "anomalyScore": 0.204989077199074, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", @@ -1738,7 +1738,7 @@ Object { "service.environment": "production", "service.name": "opbeans-python", "serviceAnomalyStats": Object { - "actualValue": 66218.08333333333, + "actualValue": 66218.0833333333, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -1770,8 +1770,8 @@ Object { "service.environment": "testing", "service.name": "opbeans-go", "serviceAnomalyStats": Object { - "actualValue": 3933482.1764705875, - "anomalyScore": 2.6101702751482714, + "actualValue": 3933482.17647059, + "anomalyScore": 2.61017027514827, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", @@ -1802,7 +1802,7 @@ Object { "service.environment": "production", "service.name": "opbeans-java", "serviceAnomalyStats": Object { - "actualValue": 14901.319999999996, + "actualValue": 14901.32, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -1834,7 +1834,7 @@ Object { "service.environment": "testing", "service.name": "opbeans-node", "serviceAnomalyStats": Object { - "actualValue": 32226.649122807008, + "actualValue": 32226.649122807, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", @@ -1866,7 +1866,7 @@ Object { "service.environment": "production", "service.name": "opbeans-python", "serviceAnomalyStats": Object { - "actualValue": 66218.08333333333, + "actualValue": 66218.0833333333, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -1898,8 +1898,8 @@ Object { "service.environment": "production", "service.name": "opbeans-ruby", "serviceAnomalyStats": Object { - "actualValue": 684716.5813953485, - "anomalyScore": 0.20498907719907372, + "actualValue": 684716.581395349, + "anomalyScore": 0.204989077199074, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", @@ -1928,7 +1928,7 @@ Object { "service.environment": "production", "service.name": "opbeans-python", "serviceAnomalyStats": Object { - "actualValue": 66218.08333333333, + "actualValue": 66218.0833333333, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -1943,7 +1943,7 @@ Object { "service.environment": "production", "service.name": "opbeans-java", "serviceAnomalyStats": Object { - "actualValue": 14901.319999999996, + "actualValue": 14901.32, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -1958,7 +1958,7 @@ Object { "service.environment": "testing", "service.name": "opbeans-node", "serviceAnomalyStats": Object { - "actualValue": 32226.649122807008, + "actualValue": 32226.649122807, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", @@ -1973,8 +1973,8 @@ Object { "service.environment": "testing", "service.name": "opbeans-go", "serviceAnomalyStats": Object { - "actualValue": 3933482.1764705875, - "anomalyScore": 2.6101702751482714, + "actualValue": 3933482.17647059, + "anomalyScore": 2.61017027514827, "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", @@ -1988,8 +1988,8 @@ Object { "service.environment": "production", "service.name": "opbeans-ruby", "serviceAnomalyStats": Object { - "actualValue": 684716.5813953485, - "anomalyScore": 0.20498907719907372, + "actualValue": 684716.581395349, + "anomalyScore": 0.204989077199074, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", diff --git a/x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts b/x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts index 2e4a859f08cca..a8632d7a27c3c 100644 --- a/x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts +++ b/x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts @@ -177,7 +177,7 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext) "service.environment": "production", "service.name": "opbeans-python", "serviceAnomalyStats": Object { - "actualValue": 66218.08333333333, + "actualValue": 66218.0833333333, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", @@ -192,7 +192,7 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext) "service.environment": "production", "service.name": "opbeans-java", "serviceAnomalyStats": Object { - "actualValue": 14901.319999999996, + "actualValue": 14901.32, "anomalyScore": 0, "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", diff --git a/x-pack/test/functional/apps/lens/smokescreen.ts b/x-pack/test/functional/apps/lens/smokescreen.ts index 42807a23cb13a..05047fab2517d 100644 --- a/x-pack/test/functional/apps/lens/smokescreen.ts +++ b/x-pack/test/functional/apps/lens/smokescreen.ts @@ -13,7 +13,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const listingTable = getService('listingTable'); const testSubjects = getService('testSubjects'); - describe('lens smokescreen tests', () => { + // Failing: See https://github.com/elastic/kibana/issues/77969 + describe.skip('lens smokescreen tests', () => { it('should allow creation of lens xy chart', async () => { await PageObjects.visualize.navigateToNewVisualization(); await PageObjects.visualize.clickVisType('lens'); diff --git a/x-pack/test/functional/apps/maps/index.js b/x-pack/test/functional/apps/maps/index.js index 03b75601ec2a8..2d2d2f9d3cf9b 100644 --- a/x-pack/test/functional/apps/maps/index.js +++ b/x-pack/test/functional/apps/maps/index.js @@ -47,6 +47,7 @@ export default function ({ loadTestFile, getService }) { loadTestFile(require.resolve('./es_pew_pew_source')); loadTestFile(require.resolve('./joins')); loadTestFile(require.resolve('./mvt_scaling')); + loadTestFile(require.resolve('./mvt_super_fine')); loadTestFile(require.resolve('./add_layer_panel')); loadTestFile(require.resolve('./import_geojson')); loadTestFile(require.resolve('./layer_errors')); diff --git a/x-pack/test/functional/apps/maps/mvt_super_fine.js b/x-pack/test/functional/apps/maps/mvt_super_fine.js new file mode 100644 index 0000000000000..b5a7935a81eb5 --- /dev/null +++ b/x-pack/test/functional/apps/maps/mvt_super_fine.js @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +const MB_VECTOR_SOURCE_ID = 'g1xkv'; + +export default function ({ getPageObjects, getService }) { + const PageObjects = getPageObjects(['maps']); + const inspector = getService('inspector'); + const security = getService('security'); + + describe('mvt grid layer', () => { + before(async () => { + await security.testUser.setRoles( + ['global_maps_all', 'test_logstash_reader', 'geoshape_data_reader'], + false + ); + await PageObjects.maps.loadSavedMap('geo grid vector grid example (SUPER_FINE resolution)'); + }); + + after(async () => { + await inspector.close(); + await security.testUser.restoreDefaults(); + }); + + it('should render with mvt-source', async () => { + const mapboxStyle = await PageObjects.maps.getMapboxStyle(); + + //Source should be correct + expect(mapboxStyle.sources[MB_VECTOR_SOURCE_ID].tiles[0]).to.equal( + "/api/maps/mvt/getGridTile?x={x}&y={y}&z={z}&geometryFieldName=geo.coordinates&index=logstash-*&requestBody=(_source:(excludes:!()),aggs:(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:geo.coordinates)),max_of_bytes:(max:(field:bytes))),geotile_grid:(bounds:!n,field:geo.coordinates,precision:!n,shard_size:65535,size:65535))),docvalue_fields:!((field:'@timestamp',format:date_time),(field:'relatedContent.article:modified_time',format:date_time),(field:'relatedContent.article:published_time',format:date_time),(field:utc_time,format:date_time)),query:(bool:(filter:!((match_all:()),(range:('@timestamp':(format:strict_date_optional_time,gte:'2015-09-20T00:00:00.000Z',lte:'2015-09-20T01:00:00.000Z')))),must:!(),must_not:!(),should:!())),script_fields:(hour_of_day:(script:(lang:painless,source:'doc[!'@timestamp!'].value.getHour()'))),size:0,stored_fields:!('*'))&requestType=grid&geoFieldType=geo_point" + ); + + //Should correctly load meta for style-rule (sigma is set to 1, opacity to 1) + const fillLayer = mapboxStyle.layers.find( + (layer) => layer.id === MB_VECTOR_SOURCE_ID + '_fill' + ); + + expect(fillLayer.paint).to.eql({ + 'fill-color': [ + 'interpolate', + ['linear'], + [ + 'coalesce', + [ + 'case', + ['==', ['get', 'max_of_bytes'], null], + 1622, + ['max', ['min', ['to-number', ['get', 'max_of_bytes']], 9790], 1623], + ], + 1622, + ], + 1622, + 'rgba(0,0,0,0)', + 1623, + '#ecf1f7', + 2643.875, + '#d9e3ef', + 3664.75, + '#c5d5e7', + 4685.625, + '#b2c7df', + 5706.5, + '#9eb9d8', + 6727.375, + '#8bacd0', + 7748.25, + '#769fc8', + 8769.125, + '#6092c0', + ], + 'fill-opacity': 0.75, + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/ml/index.ts b/x-pack/test/functional/apps/ml/index.ts index e224f5c8bb128..74dc0fc3ca9f0 100644 --- a/x-pack/test/functional/apps/ml/index.ts +++ b/x-pack/test/functional/apps/ml/index.ts @@ -46,5 +46,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./anomaly_detection')); loadTestFile(require.resolve('./data_visualizer')); loadTestFile(require.resolve('./data_frame_analytics')); + loadTestFile(require.resolve('./settings')); }); } diff --git a/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts b/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts index eed7489b09fe6..c3dde872fa4a6 100644 --- a/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts +++ b/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts @@ -438,7 +438,7 @@ export default function ({ getService }: FtrProviderContext) { 'should display enabled elements of the edit calendar page' ); await ml.settingsFilterList.assertEditDescriptionButtonEnabled(true); - await ml.settingsFilterList.assertAddItemButtonEnabled(true); + await ml.settingsFilterList.assertAddItemsButtonEnabled(true); await ml.testExecution.logTestStep('should display the filter item in the list'); await ml.settingsFilterList.assertFilterItemExists(filterItems[0]); diff --git a/x-pack/test/functional/apps/ml/settings/calendar_creation.ts b/x-pack/test/functional/apps/ml/settings/calendar_creation.ts new file mode 100644 index 0000000000000..5b1e3b0a12b13 --- /dev/null +++ b/x-pack/test/functional/apps/ml/settings/calendar_creation.ts @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { asyncForEach, createJobConfig } from './common'; + +export default function ({ getService }: FtrProviderContext) { + const ml = getService('ml'); + const esArchiver = getService('esArchiver'); + + const calendarId = 'test_calendar_id'; + const jobConfigs = [createJobConfig('test_calendar_ad_1'), createJobConfig('test_calendar_ad_2')]; + + describe('calendar creation', function () { + before(async () => { + await esArchiver.loadIfNeeded('ml/farequote'); + await ml.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp'); + + await asyncForEach(jobConfigs, async (jobConfig) => { + await ml.api.createAnomalyDetectionJob(jobConfig); + }); + await ml.testResources.setKibanaTimeZoneToUTC(); + await ml.securityUI.loginAsMlPowerUser(); + }); + + after(async () => { + await ml.api.cleanMlIndices(); + }); + + afterEach(async () => { + await ml.api.deleteCalendar(calendarId); + }); + + it('creates new calendar that applies to all jobs', async () => { + await ml.testExecution.logTestStep('calendar creation loads the calendar management page'); + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToSettings(); + await ml.settings.navigateToCalendarManagement(); + + await ml.testExecution.logTestStep('calendar creation loads the new calendar edit page'); + await ml.settingsCalendar.assertCreateCalendarButtonEnabled(true); + await ml.settingsCalendar.navigateToCalendarCreationPage(); + + await ml.testExecution.logTestStep('calendar creation sets calendar to apply to all jobs'); + await ml.settingsCalendar.toggleApplyToAllJobsSwitch(true); + await ml.settingsCalendar.assertJobSelectionNotExists(); + await ml.settingsCalendar.assertJobGroupSelectionNotExists(); + + await ml.testExecution.logTestStep('calendar creation sets the calendar id and description'); + await ml.settingsCalendar.setCalendarId(calendarId); + await ml.settingsCalendar.setCalendarDescription('test calendar description'); + + await ml.testExecution.logTestStep('calendar creation creates new calendar event'); + await ml.settingsCalendar.openNewCalendarEventForm(); + await ml.settingsCalendar.setCalendarEventDescription('holiday'); + await ml.settingsCalendar.addNewCalendarEvent(); + await ml.settingsCalendar.assertEventRowExists('holiday'); + + await ml.testExecution.logTestStep( + 'calendar creation saves the new calendar and displays it in the list of calendars ' + ); + await ml.settingsCalendar.saveCalendar(); + + await ml.settingsCalendar.assertCalendarRowExists(calendarId); + }); + + it('creates new calendar that applies to specific jobs', async () => { + await ml.testExecution.logTestStep('calendar creation loads the calendar management page'); + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToSettings(); + await ml.settings.navigateToCalendarManagement(); + + await ml.testExecution.logTestStep('calendar creation loads the new calendar edit page'); + await ml.settingsCalendar.assertCreateCalendarButtonEnabled(true); + await ml.settingsCalendar.navigateToCalendarCreationPage(); + + await ml.testExecution.logTestStep( + 'calendar creation verifies the job selection and job group section are displayed' + ); + await ml.settingsCalendar.assertJobSelectionExists(); + await ml.settingsCalendar.assertJobSelectionEnabled(true); + await ml.settingsCalendar.assertJobGroupSelectionExists(); + await ml.settingsCalendar.assertJobGroupSelectionEnabled(true); + + await ml.testExecution.logTestStep('calendar creation sets the calendar id'); + await ml.settingsCalendar.setCalendarId(calendarId); + + await ml.testExecution.logTestStep('calendar creation sets the job selection'); + await asyncForEach(jobConfigs, async (jobConfig) => { + await ml.settingsCalendar.selectJob(jobConfig.job_id); + }); + + await ml.settingsCalendar.saveCalendar(); + await ml.settingsCalendar.assertCalendarRowExists(calendarId); + }); + }); +} diff --git a/x-pack/test/functional/apps/ml/settings/calendar_delete.ts b/x-pack/test/functional/apps/ml/settings/calendar_delete.ts new file mode 100644 index 0000000000000..2cc4f91d5528f --- /dev/null +++ b/x-pack/test/functional/apps/ml/settings/calendar_delete.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { asyncForEach } from './common'; + +export default function ({ getService }: FtrProviderContext) { + const ml = getService('ml'); + + const testDataList = [1, 2].map((n) => ({ + calendarId: `test_delete_calendar_${n}`, + description: `test description ${n}`, + })); + + describe('calendar delete', function () { + before(async () => { + await ml.testResources.setKibanaTimeZoneToUTC(); + await ml.securityUI.loginAsMlPowerUser(); + + await asyncForEach(testDataList, async ({ calendarId, description }) => { + await ml.api.createCalendar(calendarId, { + description, + }); + }); + }); + + after(async () => { + await ml.api.cleanMlIndices(); + + // clean up created calendars + await asyncForEach(testDataList, async ({ calendarId }) => { + await ml.api.deleteCalendar(calendarId); + }); + }); + + it('deletes multiple calendars', async () => { + await ml.testExecution.logTestStep('calendar delete loads the calendar list management page'); + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToSettings(); + await ml.settings.navigateToCalendarManagement(); + + await ml.testExecution.logTestStep('calendar delete selects multiple calendars for deletion'); + await asyncForEach(testDataList, async ({ calendarId }) => { + await ml.settingsCalendar.assertCalendarRowExists(calendarId); + await ml.settingsCalendar.selectCalendarRow(calendarId); + }); + + await ml.testExecution.logTestStep('calendar delete clicks the delete button'); + await ml.settingsCalendar.deleteCalendar(); + + await ml.testExecution.logTestStep( + 'calendar delete validates the calendars are deleted from the table' + ); + await asyncForEach(testDataList, async ({ calendarId }) => { + await ml.settingsCalendar.assertCalendarRowNotExists(calendarId); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/ml/settings/calendar_edit.ts b/x-pack/test/functional/apps/ml/settings/calendar_edit.ts new file mode 100644 index 0000000000000..f7c8c1f6f85f5 --- /dev/null +++ b/x-pack/test/functional/apps/ml/settings/calendar_edit.ts @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { asyncForEach, createJobConfig } from './common'; + +export default function ({ getService }: FtrProviderContext) { + const ml = getService('ml'); + const esArchiver = getService('esArchiver'); + const comboBox = getService('comboBox'); + + const calendarId = 'test_edit_calendar_id'; + const testEvents = [ + { description: 'event_1', start_time: 1513641600000, end_time: 1513728000000 }, + { description: 'event_2', start_time: 1513814400000, end_time: 1513900800000 }, + ]; + const jobConfigs = [createJobConfig('test_calendar_ad_1'), createJobConfig('test_calendar_ad_2')]; + const newJobGroups = ['farequote']; + + describe('calendar edit', function () { + before(async () => { + await esArchiver.loadIfNeeded('ml/farequote'); + await ml.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp'); + + await asyncForEach(jobConfigs, async (jobConfig) => { + await ml.api.createAnomalyDetectionJob(jobConfig); + }); + + await ml.api.createCalendar(calendarId, { + job_ids: jobConfigs.map((c) => c.job_id), + description: 'Test calendar', + }); + await ml.api.createCalendarEvents(calendarId, testEvents); + + await ml.testResources.setKibanaTimeZoneToUTC(); + await ml.securityUI.loginAsMlPowerUser(); + }); + + after(async () => { + await ml.api.cleanMlIndices(); + }); + + afterEach(async () => { + await ml.api.deleteCalendar(calendarId); + }); + + it('updates jobs, groups and events', async () => { + await ml.testExecution.logTestStep('calendar edit loads the calendar management page'); + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToSettings(); + await ml.settings.navigateToCalendarManagement(); + + await ml.testExecution.logTestStep('calendar edit opens existing calendar'); + await ml.settingsCalendar.openCalendarEditForm(calendarId); + + await ml.testExecution.logTestStep( + 'calendar edit deselects previous job selection and assigns new job groups' + ); + await comboBox.clear('mlCalendarJobSelection'); + await asyncForEach(newJobGroups, async (newJobGroup) => { + await ml.settingsCalendar.selectJobGroup(newJobGroup); + }); + + await ml.testExecution.logTestStep('calendar edit deletes old events'); + + await asyncForEach(testEvents, async ({ description }) => { + await ml.settingsCalendar.deleteCalendarEventRow(description); + }); + + await ml.testExecution.logTestStep('calendar edit creates new calendar event'); + await ml.settingsCalendar.openNewCalendarEventForm(); + await ml.settingsCalendar.setCalendarEventDescription('holiday'); + await ml.settingsCalendar.addNewCalendarEvent(); + await ml.settingsCalendar.assertEventRowExists('holiday'); + + await ml.testExecution.logTestStep( + 'calendar edit saves the new calendar and displays it in the list of calendars ' + ); + await ml.settingsCalendar.saveCalendar(); + await ml.settingsCalendar.assertCalendarRowExists(calendarId); + + await ml.testExecution.logTestStep('calendar edit re-opens the updated calendar'); + await ml.settingsCalendar.openCalendarEditForm(calendarId); + await ml.testExecution.logTestStep('calendar edit verifies the job selection is empty'); + await ml.settingsCalendar.assertJobSelection([]); + await ml.testExecution.logTestStep( + 'calendar edit verifies the job group selection was updated' + ); + await ml.settingsCalendar.assertJobGroupSelection(newJobGroups); + + await ml.testExecution.logTestStep('calendar edit verifies calendar updated correctly'); + await asyncForEach(testEvents, async ({ description }) => { + await ml.settingsCalendar.assertEventRowMissing(description); + }); + await ml.settingsCalendar.assertEventRowExists('holiday'); + }); + }); +} diff --git a/x-pack/test/functional/apps/ml/settings/common.ts b/x-pack/test/functional/apps/ml/settings/common.ts new file mode 100644 index 0000000000000..9fada028ff3da --- /dev/null +++ b/x-pack/test/functional/apps/ml/settings/common.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export async function asyncForEach(array: T[], callback: (item: T, index: number) => void) { + for (let index = 0; index < array.length; index++) { + await callback(array[index], index); + } +} + +export const createJobConfig = (jobId: string) => ({ + job_id: jobId, + description: + 'mean/min/max(responsetime) partition=airline on farequote dataset with 1h bucket span', + groups: ['farequote', 'automated', 'multi-metric'], + analysis_config: { + bucket_span: '1h', + influencers: ['airline'], + detectors: [{ function: 'mean', field_name: 'responsetime', partition_field_name: 'airline' }], + }, + data_description: { time_field: '@timestamp' }, + analysis_limits: { model_memory_limit: '20mb' }, + model_plot_config: { enabled: false }, +}); diff --git a/x-pack/test/functional/apps/ml/settings/filter_list_creation.ts b/x-pack/test/functional/apps/ml/settings/filter_list_creation.ts new file mode 100644 index 0000000000000..22affa1cada38 --- /dev/null +++ b/x-pack/test/functional/apps/ml/settings/filter_list_creation.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const ml = getService('ml'); + const filterId = 'test_create_filter'; + const description = 'test description'; + const keywords = ['filter word 1', 'filter word 2', 'filter word 3']; + + describe('filter list creation', function () { + before(async () => { + await ml.testResources.setKibanaTimeZoneToUTC(); + await ml.securityUI.loginAsMlPowerUser(); + }); + + after(async () => { + await ml.api.cleanMlIndices(); + // clean up created filters + await ml.api.deleteFilter(filterId); + }); + + it('creates new filter list', async () => { + await ml.testExecution.logTestStep( + 'filter list creation loads the filter list management page' + ); + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToSettings(); + await ml.settings.navigateToFilterListsManagement(); + + await ml.testExecution.logTestStep('filter list creation loads the filter creation page'); + await ml.settingsFilterList.navigateToFilterListCreationPage(); + + await ml.testExecution.logTestStep('filter list creation sets the list name and description'); + await ml.settingsFilterList.setFilterListId(filterId); + await ml.settingsFilterList.setFilterListDescription(description); + + await ml.testExecution.logTestStep('filter list creation adds items to the filter list'); + await ml.settingsFilterList.addFilterListKeywords(keywords); + await ml.testExecution.logTestStep('filter list creation saves the settings'); + await ml.settingsFilterList.saveFilterList(); + await ml.settingsFilterList.assertFilterListRowExists(filterId); + }); + }); +} diff --git a/x-pack/test/functional/apps/ml/settings/filter_list_delete.ts b/x-pack/test/functional/apps/ml/settings/filter_list_delete.ts new file mode 100644 index 0000000000000..9e30d2c8915d2 --- /dev/null +++ b/x-pack/test/functional/apps/ml/settings/filter_list_delete.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { asyncForEach } from './common'; + +export default function ({ getService }: FtrProviderContext) { + const ml = getService('ml'); + + const testDataList = [1, 2].map((n) => ({ + filterId: `test_delete_filter_${n}`, + description: `test description ${n}`, + items: ['filter word 1', 'filter word 2', 'filter word 3'], + })); + + describe('filter list delete', function () { + before(async () => { + await ml.testResources.setKibanaTimeZoneToUTC(); + await ml.securityUI.loginAsMlPowerUser(); + + for (let index = 0; index < testDataList.length; index++) { + const { filterId, description, items } = testDataList[index]; + + await ml.api.createFilter(filterId, { + description, + items, + }); + } + }); + + after(async () => { + await ml.api.cleanMlIndices(); + + // clean up created filters + await asyncForEach(testDataList, async ({ filterId }) => { + await ml.api.deleteFilter(filterId); + }); + }); + + it('deletes filter list with items', async () => { + await ml.testExecution.logTestStep( + 'filter list delete loads the filter list management page' + ); + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToSettings(); + await ml.settings.navigateToFilterListsManagement(); + + await ml.testExecution.logTestStep( + 'filter list delete selects list entries and deletes them' + ); + for (const testData of testDataList) { + const { filterId } = testData; + await ml.settingsFilterList.selectFilterListRow(filterId); + } + await ml.settingsFilterList.deleteFilterList(); + + await ml.testExecution.logTestStep( + 'filter list delete validates selected filter lists are deleted' + ); + await asyncForEach(testDataList, async ({ filterId }) => { + await ml.settingsFilterList.assertFilterListRowNotExists(filterId); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/ml/settings/filter_list_edit.ts b/x-pack/test/functional/apps/ml/settings/filter_list_edit.ts new file mode 100644 index 0000000000000..8c39c679ac6f2 --- /dev/null +++ b/x-pack/test/functional/apps/ml/settings/filter_list_edit.ts @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { asyncForEach } from './common'; + +export default function ({ getService }: FtrProviderContext) { + const ml = getService('ml'); + + const filterId = 'test_filter_list_edit'; + const keywordToDelete = 'keyword_to_delete'; + const oldKeyword = 'old_keyword'; + const oldDescription = 'Old filter list description'; + + const newKeywords = ['new_keyword1', 'new_keyword2']; + const newDescription = 'New filter list description'; + + describe('filter list edit', function () { + before(async () => { + await ml.testResources.setKibanaTimeZoneToUTC(); + await ml.securityUI.loginAsMlPowerUser(); + + await ml.api.createFilter(filterId, { + description: oldDescription, + items: [keywordToDelete, oldKeyword], + }); + }); + + after(async () => { + await ml.api.cleanMlIndices(); + await ml.api.deleteFilter(filterId); + }); + + it('updates description and filter items', async () => { + await ml.testExecution.logTestStep('filter list edit loads the filter list management page'); + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToSettings(); + await ml.settings.navigateToFilterListsManagement(); + + await ml.testExecution.logTestStep('filter list edit opens existing filter list'); + await ml.settingsFilterList.selectFilterListRowEditLink(filterId); + await ml.settingsFilterList.assertFilterItemExists(keywordToDelete); + await ml.settingsFilterList.assertFilterListDescriptionValue(oldDescription); + + await ml.testExecution.logTestStep('filter list edit deletes existing filter item'); + await ml.settingsFilterList.deleteFilterItem(keywordToDelete); + + await ml.testExecution.logTestStep('filter list edit sets new keywords and description'); + await ml.settingsFilterList.setFilterListDescription(newDescription); + await ml.settingsFilterList.addFilterListKeywords(newKeywords); + + await ml.testExecution.logTestStep( + 'filter list edit saves the new filter list and displays it in the list of entries' + ); + await ml.settingsFilterList.saveFilterList(); + await ml.settingsFilterList.assertFilterListRowExists(filterId); + + await ml.testExecution.logTestStep('filter list edit reopens the edited filter list'); + await ml.settingsFilterList.selectFilterListRowEditLink(filterId); + + await ml.testExecution.logTestStep( + 'filter list edit verifies the filter list description updated correctly' + ); + await ml.settingsFilterList.assertFilterListDescriptionValue(newDescription); + + await ml.testExecution.logTestStep( + 'filter list edit verifies the filter items updated correctly' + ); + await ml.settingsFilterList.assertFilterItemNotExists(keywordToDelete); + await asyncForEach([...newKeywords, oldKeyword], async (filterItem) => { + await ml.settingsFilterList.assertFilterItemExists(filterItem); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/ml/settings/index.ts b/x-pack/test/functional/apps/ml/settings/index.ts new file mode 100644 index 0000000000000..5b2c7d15e1959 --- /dev/null +++ b/x-pack/test/functional/apps/ml/settings/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('settings', function () { + this.tags(['quynh', 'skipFirefox']); + + loadTestFile(require.resolve('./calendar_creation')); + loadTestFile(require.resolve('./calendar_edit')); + loadTestFile(require.resolve('./calendar_delete')); + + loadTestFile(require.resolve('./filter_list_creation')); + loadTestFile(require.resolve('./filter_list_edit')); + loadTestFile(require.resolve('./filter_list_delete')); + }); +} diff --git a/x-pack/test/functional/apps/monitoring/cluster/overview.js b/x-pack/test/functional/apps/monitoring/cluster/overview.js index 0e608e9a055fa..e30280802e8c8 100644 --- a/x-pack/test/functional/apps/monitoring/cluster/overview.js +++ b/x-pack/test/functional/apps/monitoring/cluster/overview.js @@ -30,7 +30,7 @@ export default function ({ getService, getPageObjects }) { }); it('shows elasticsearch panel with data', async () => { - expect(await overview.getEsStatus()).to.be('Health is green'); + expect(await overview.getEsStatus()).to.be('Healthy'); expect(await overview.getEsVersion()).to.be('7.0.0-alpha1'); expect(await overview.getEsUptime()).to.be('20 minutes'); expect(await overview.getEsNumberOfNodes()).to.be('Nodes: 2'); @@ -44,7 +44,7 @@ export default function ({ getService, getPageObjects }) { }); it('shows kibana panel', async () => { - expect(await overview.getEsStatus()).to.be('Health is green'); + expect(await overview.getEsStatus()).to.be('Healthy'); expect(await overview.getKbnRequests()).to.be('914'); expect(await overview.getKbnMaxResponseTime()).to.be('2873 ms'); expect(await overview.getKbnInstances()).to.be('Instances: 1'); @@ -81,7 +81,7 @@ export default function ({ getService, getPageObjects }) { }); it('shows elasticsearch panel with data', async () => { - expect(await overview.getEsStatus()).to.be('Health is yellow'); + expect(await overview.getEsStatus()).to.be('Missing replica shards'); expect(await overview.getEsVersion()).to.be('7.0.0-alpha1'); expect(await overview.getEsUptime()).to.be('5 minutes'); expect(await overview.getEsNumberOfNodes()).to.be('Nodes: 1'); @@ -95,7 +95,7 @@ export default function ({ getService, getPageObjects }) { }); it('shows kibana panel', async () => { - expect(await overview.getKbnStatus()).to.be('Health is green'); + expect(await overview.getKbnStatus()).to.be('Healthy'); expect(await overview.getKbnRequests()).to.be('174'); expect(await overview.getKbnMaxResponseTime()).to.be('2203 ms'); expect(await overview.getKbnInstances()).to.be('Instances: 1'); @@ -131,7 +131,7 @@ export default function ({ getService, getPageObjects }) { }); it('shows elasticsearch panel with data', async () => { - expect(await overview.getEsStatus()).to.be('Health is yellow'); + expect(await overview.getEsStatus()).to.be('Missing replica shards'); expect(await overview.getEsVersion()).to.be('7.0.0-alpha1'); expect(await overview.getEsUptime()).to.be('8 minutes'); expect(await overview.getEsNumberOfNodes()).to.be('Nodes: 1'); diff --git a/x-pack/test/functional/apps/monitoring/elasticsearch/nodes.js b/x-pack/test/functional/apps/monitoring/elasticsearch/nodes.js index 569447df0533f..8ae1f89c2f3b4 100644 --- a/x-pack/test/functional/apps/monitoring/elasticsearch/nodes.js +++ b/x-pack/test/functional/apps/monitoring/elasticsearch/nodes.js @@ -56,23 +56,39 @@ export default function ({ getService, getPageObjects }) { expect(rows.length).to.be(3); const nodesAll = await nodesList.getNodesAll(); + console.log(JSON.stringify(nodesAll, null, 2)); const tableData = [ { name: 'whatever-01', status: 'Status: Online', - cpu: '0% \n3% max\n0% min', - load: '3.28 \n3.71 max\n2.19 min', - memory: '39% \n52% max\n25% min', - disk: '173.9 GB \n173.9 GB max\n173.9 GB min', + cpu: '0%', + cpuText: 'Trending\nup\nMax value\n3%\nMin value\n0%\nApplies to current time period', + load: '3.28', + loadText: + 'Trending\nup\nMax value\n3.71\nMin value\n2.19\nApplies to current time period', + memory: '39%', + memoryText: + 'Trending\ndown\nMax value\n52%\nMin value\n25%\nApplies to current time period', + disk: '173.9 GB', + diskText: + 'Trending\ndown\nMax value\n173.9 GB\nMin value\n173.9 GB\nApplies to current time period', shards: '38', }, { name: 'whatever-02', status: 'Status: Online', - cpu: '2% \n3% max\n0% min', - load: '3.28 \n3.73 max\n2.29 min', - memory: '25% \n49% max\n25% min', - disk: '173.9 GB \n173.9 GB max\n173.9 GB min', + cpu: '2%', + cpuText: + 'Trending\ndown\nMax value\n3%\nMin value\n0%\nApplies to current time period', + load: '3.28', + loadText: + 'Trending\nup\nMax value\n3.73\nMin value\n2.29\nApplies to current time period', + memory: '25%', + memoryText: + 'Trending\ndown\nMax value\n49%\nMin value\n25%\nApplies to current time period', + disk: '173.9 GB', + diskText: + 'Trending\ndown\nMax value\n173.9 GB\nMin value\n173.9 GB\nApplies to current time period', shards: '38', }, { name: 'whatever-03', status: 'Status: Offline' }, @@ -81,9 +97,13 @@ export default function ({ getService, getPageObjects }) { expect(nodesAll[node].name).to.be(tableData[node].name); expect(nodesAll[node].status).to.be(tableData[node].status); expect(nodesAll[node].cpu).to.be(tableData[node].cpu); + expect(nodesAll[node].cpuText).to.be(tableData[node].cpuText); expect(nodesAll[node].load).to.be(tableData[node].load); + expect(nodesAll[node].loadText).to.be(tableData[node].loadText); expect(nodesAll[node].memory).to.be(tableData[node].memory); + expect(nodesAll[node].memoryText).to.be(tableData[node].memoryText); expect(nodesAll[node].disk).to.be(tableData[node].disk); + expect(nodesAll[node].diskText).to.be(tableData[node].diskText); expect(nodesAll[node].shards).to.be(tableData[node].shards); }); }); @@ -94,12 +114,20 @@ export default function ({ getService, getPageObjects }) { const nodesAll = await nodesList.getNodesAll(); const tableData = [ - { cpu: '2% \n3% max\n0% min' }, - { cpu: '0% \n3% max\n0% min' }, - { cpu: undefined }, + { + cpu: '2%', + cpuText: + 'Trending\ndown\nMax value\n3%\nMin value\n0%\nApplies to current time period', + }, + { + cpu: '0%', + cpuText: 'Trending\nup\nMax value\n3%\nMin value\n0%\nApplies to current time period', + }, + { cpu: undefined, cpuText: undefined }, ]; nodesAll.forEach((obj, node) => { expect(nodesAll[node].cpu).to.be(tableData[node].cpu); + expect(nodesAll[node].cpuText).to.be(tableData[node].cpuText); }); }); @@ -109,12 +137,21 @@ export default function ({ getService, getPageObjects }) { const nodesAll = await nodesList.getNodesAll(); const tableData = [ - { load: '3.28 \n3.71 max\n2.19 min' }, - { load: '3.28 \n3.73 max\n2.29 min' }, + { + load: '3.28', + loadText: + 'Trending\nup\nMax value\n3.71\nMin value\n2.19\nApplies to current time period', + }, + { + load: '3.28', + loadText: + 'Trending\nup\nMax value\n3.73\nMin value\n2.29\nApplies to current time period', + }, { load: undefined }, ]; nodesAll.forEach((obj, node) => { expect(nodesAll[node].load).to.be(tableData[node].load); + expect(nodesAll[node].loadText).to.be(tableData[node].loadText); }); }); }); @@ -155,12 +192,21 @@ export default function ({ getService, getPageObjects }) { const nodesAll = await nodesList.getNodesAll(); const tableData = [ - { memory: '39% \n52% max\n25% min' }, - { memory: '25% \n49% max\n25% min' }, - { memory: undefined }, + { + memory: '39%', + memoryText: + 'Trending\ndown\nMax value\n52%\nMin value\n25%\nApplies to current time period', + }, + { + memory: '25%', + memoryText: + 'Trending\ndown\nMax value\n49%\nMin value\n25%\nApplies to current time period', + }, + { memory: undefined, memoryText: undefined }, ]; nodesAll.forEach((obj, node) => { expect(nodesAll[node].memory).to.be(tableData[node].memory); + expect(nodesAll[node].memoryText).to.be(tableData[node].memoryText); }); }); @@ -170,12 +216,21 @@ export default function ({ getService, getPageObjects }) { const nodesAll = await nodesList.getNodesAll(); const tableData = [ - { disk: '173.9 GB \n173.9 GB max\n173.9 GB min' }, - { disk: '173.9 GB \n173.9 GB max\n173.9 GB min' }, + { + disk: '173.9 GB', + diskText: + 'Trending\ndown\nMax value\n173.9 GB\nMin value\n173.9 GB\nApplies to current time period', + }, + { + disk: '173.9 GB', + diskText: + 'Trending\ndown\nMax value\n173.9 GB\nMin value\n173.9 GB\nApplies to current time period', + }, { disk: undefined }, ]; nodesAll.forEach((obj, node) => { expect(nodesAll[node].disk).to.be(tableData[node].disk); + expect(nodesAll[node].diskText).to.be(tableData[node].diskText); }); }); diff --git a/x-pack/test/functional/es_archives/fleet/agents/data.json b/x-pack/test/functional/es_archives/fleet/agents/data.json index 12d646de85ec3..e05a2fe010e89 100644 --- a/x-pack/test/functional/es_archives/fleet/agents/data.json +++ b/x-pack/test/functional/es_archives/fleet/agents/data.json @@ -32,6 +32,7 @@ "access_api_key_id": "api-key-2", "active": true, "shared_id": "agent2_filebeat", + "policy_id": "policy1", "type": "PERMANENT", "local_metadata": {}, "user_provided_metadata": {} @@ -54,6 +55,7 @@ "access_api_key_id": "api-key-3", "active": true, "shared_id": "agent3_metricbeat", + "policy_id": "policy1", "type": "PERMANENT", "local_metadata": {}, "user_provided_metadata": {} @@ -76,6 +78,7 @@ "access_api_key_id": "api-key-4", "active": true, "shared_id": "agent4_metricbeat", + "policy_id": "policy1", "type": "PERMANENT", "local_metadata": {}, "user_provided_metadata": {} @@ -246,3 +249,32 @@ } } } + +{ + "type": "doc", + "value": { + "id": "ingest-agent-policies:policy2", + "index": ".kibana", + "source": { + "type": "ingest-agent-policies", + "ingest-agent-policies": { + "name": "Test policy 2", + "namespace": "default", + "description": "Policy 2", + "status": "active", + "package_policies": [], + "is_default": true, + "monitoring_enabled": [ + "logs", + "metrics" + ], + "revision": 2, + "updated_at": "2020-05-07T19:34:42.533Z", + "updated_by": "system" + }, + "migrationVersion": { + "ingest-agent-policies": "7.10.0" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/maps/kibana/data.json b/x-pack/test/functional/es_archives/maps/kibana/data.json index f756d73484198..e3a8743e60897 100644 --- a/x-pack/test/functional/es_archives/maps/kibana/data.json +++ b/x-pack/test/functional/es_archives/maps/kibana/data.json @@ -615,6 +615,37 @@ } } +{ + "type": "doc", + "value": { + "id": "map:78116c8c-fd2a-11ea-adc1-0242ac120002", + "index": ".kibana", + "source": { + "map": { + "bounds": { + "coordinates": [ + [ + -140.62361, + 54.11832 + ], + [ + -55.49169, + 18.17193 + ] + ], + "type": "envelope" + }, + "description": "", + "layerListJSON": "[{\"id\":\"g1xkv\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"resolution\": \"SUPER_FINE\",\"type\":\"ES_GEO_GRID\",\"id\":\"9305f6ea-4518-4c06-95b9-33321aa38d6a\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"requestType\":\"grid\",\"metrics\":[{\"type\":\"count\"},{\"type\":\"max\",\"field\":\"bytes\"}]},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"max of bytes\",\"name\":\"max_of_bytes\",\"origin\":\"source\"},\"color\":\"Blues\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#cccccc\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"minSize\":4,\"maxSize\":32}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"TILED_VECTOR\"}]", + "mapStateJSON": "{\"zoom\":3.59,\"center\":{\"lon\":-98.05765,\"lat\":38.32288},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000}}", + "title": "geo grid vector grid example (SUPER_FINE resolution)", + "uiStateJSON": "{\"isDarkMode\":false}" + }, + "type": "map" + } + } +} + { "type": "doc", "value": { diff --git a/x-pack/test/functional/services/ml/index.ts b/x-pack/test/functional/services/ml/index.ts index 325ea41ae3977..50da8425e493d 100644 --- a/x-pack/test/functional/services/ml/index.ts +++ b/x-pack/test/functional/services/ml/index.ts @@ -78,8 +78,8 @@ export function MachineLearningProvider(context: FtrProviderContext) { const securityCommon = MachineLearningSecurityCommonProvider(context); const securityUI = MachineLearningSecurityUIProvider(context, securityCommon); const settings = MachineLearningSettingsProvider(context); - const settingsCalendar = MachineLearningSettingsCalendarProvider(context); - const settingsFilterList = MachineLearningSettingsFilterListProvider(context); + const settingsCalendar = MachineLearningSettingsCalendarProvider(context, commonUI); + const settingsFilterList = MachineLearningSettingsFilterListProvider(context, commonUI); const singleMetricViewer = MachineLearningSingleMetricViewerProvider(context); const testExecution = MachineLearningTestExecutionProvider(context); const testResources = MachineLearningTestResourcesProvider(context); diff --git a/x-pack/test/functional/services/ml/security_ui.ts b/x-pack/test/functional/services/ml/security_ui.ts index e09467ff36a34..da4324901d38e 100644 --- a/x-pack/test/functional/services/ml/security_ui.ts +++ b/x-pack/test/functional/services/ml/security_ui.ts @@ -16,7 +16,6 @@ export function MachineLearningSecurityUIProvider( return { async loginAs(user: USER) { const password = mlSecurityCommon.getPasswordForUser(user); - await PageObjects.security.forceLogout(); await PageObjects.security.login(user, password, { diff --git a/x-pack/test/functional/services/ml/settings_calendar.ts b/x-pack/test/functional/services/ml/settings_calendar.ts index 34d18c6e12c47..c269636522923 100644 --- a/x-pack/test/functional/services/ml/settings_calendar.ts +++ b/x-pack/test/functional/services/ml/settings_calendar.ts @@ -7,9 +7,15 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { MlCommonUI } from './common_ui'; -export function MachineLearningSettingsCalendarProvider({ getService }: FtrProviderContext) { +export function MachineLearningSettingsCalendarProvider( + { getService }: FtrProviderContext, + mlCommonUI: MlCommonUI +) { const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + const comboBox = getService('comboBox'); return { async parseCalendarTable() { @@ -172,6 +178,11 @@ export function MachineLearningSettingsCalendarProvider({ getService }: FtrProvi ); }, + calendarRowSelector(calendarId: string, subSelector?: string) { + const row = `~mlCalendarTable > ~row-${calendarId}`; + return !subSelector ? row : `${row} > ${subSelector}`; + }, + eventRowSelector(eventDescription: string, subSelector?: string) { const row = `~mlCalendarEventsTable > ~row-${eventDescription}`; return !subSelector ? row : `${row} > ${subSelector}`; @@ -181,6 +192,10 @@ export function MachineLearningSettingsCalendarProvider({ getService }: FtrProvi await testSubjects.existOrFail(this.eventRowSelector(eventDescription)); }, + async assertEventRowMissing(eventDescription: string) { + await testSubjects.missingOrFail(this.eventRowSelector(eventDescription)); + }, + async assertDeleteEventButtonEnabled(eventDescription: string, expectedValue: boolean) { const isEnabled = await testSubjects.isEnabled( this.eventRowSelector(eventDescription, 'mlCalendarEventDeleteButton') @@ -192,5 +207,191 @@ export function MachineLearningSettingsCalendarProvider({ getService }: FtrProvi }' (got '${isEnabled ? 'enabled' : 'disabled'}')` ); }, + + async assertCalendarRowExists(calendarId: string) { + await testSubjects.existOrFail(this.calendarRowSelector(calendarId)); + }, + + async assertCalendarRowNotExists(calendarId: string) { + await testSubjects.missingOrFail(this.calendarRowSelector(calendarId)); + }, + + async assertCalendarIdValue(expectedValue: string) { + const actualValue = await testSubjects.getAttribute('mlCalendarIdInput', 'value'); + expect(actualValue).to.eql( + expectedValue, + `Calendar id should be '${expectedValue}' (got '${actualValue}')` + ); + }, + + async setCalendarId(calendarId: string) { + await mlCommonUI.setValueWithChecks('mlCalendarIdInput', calendarId, { + clearWithKeyboard: true, + }); + await this.assertCalendarIdValue(calendarId); + }, + + async assertCalendarDescriptionValue(expectedValue: string) { + const actualValue = await testSubjects.getAttribute('mlCalendarDescriptionInput', 'value'); + expect(actualValue).to.eql( + expectedValue, + `Calendar description should be '${expectedValue}' (got '${actualValue}')` + ); + }, + + async setCalendarDescription(description: string) { + await mlCommonUI.setValueWithChecks('mlCalendarDescriptionInput', description, { + clearWithKeyboard: true, + }); + await this.assertCalendarDescriptionValue(description); + }, + + async getApplyToAllJobsSwitchCheckedState(): Promise { + const subj = 'mlCalendarApplyToAllJobsSwitch'; + const isSelected = await testSubjects.getAttribute(subj, 'aria-checked'); + return isSelected === 'true'; + }, + + async toggleApplyToAllJobsSwitch(toggle: boolean) { + const subj = 'mlCalendarApplyToAllJobsSwitch'; + if ((await this.getApplyToAllJobsSwitchCheckedState()) !== toggle) { + await retry.tryForTime(5 * 1000, async () => { + await testSubjects.clickWhenNotDisabled(subj); + await this.assertApplyToAllJobsSwitchEnabled(toggle); + }); + } + }, + + async saveCalendar() { + await testSubjects.existOrFail('mlSaveCalendarButton'); + await testSubjects.click('mlSaveCalendarButton'); + await testSubjects.existOrFail('mlPageCalendarManagement'); + }, + + async navigateToCalendarCreationPage() { + await testSubjects.existOrFail('mlCalendarButtonCreate'); + await testSubjects.click('mlCalendarButtonCreate'); + await testSubjects.existOrFail('mlPageCalendarEdit'); + }, + + async openNewCalendarEventForm() { + await testSubjects.existOrFail('mlCalendarNewEventButton'); + await testSubjects.click('mlCalendarNewEventButton'); + await testSubjects.existOrFail('mlPageCalendarEdit'); + }, + + async assertCalendarEventDescriptionValue(expectedValue: string) { + const actualValue = await testSubjects.getAttribute( + 'mlCalendarEventDescriptionInput', + 'value' + ); + expect(actualValue).to.eql( + expectedValue, + `Calendar event description should be '${expectedValue}' (got '${actualValue}')` + ); + }, + + async setCalendarEventDescription(eventDescription: string) { + await testSubjects.existOrFail('mlCalendarEventDescriptionInput'); + await mlCommonUI.setValueWithChecks('mlCalendarEventDescriptionInput', eventDescription, { + clearWithKeyboard: true, + }); + await this.assertCalendarEventDescriptionValue(eventDescription); + }, + + async cancelNewCalendarEvent() { + await testSubjects.existOrFail('mlCalendarCancelEventButton'); + await testSubjects.click('mlCalendarCancelEventButton'); + await testSubjects.missingOrFail('mlCalendarEventForm'); + }, + + async addNewCalendarEvent() { + await testSubjects.existOrFail('mlCalendarAddEventButton'); + await testSubjects.click('mlCalendarAddEventButton'); + await testSubjects.missingOrFail('mlCalendarEventForm'); + }, + + async assertJobSelectionExists() { + await testSubjects.existOrFail('mlCalendarJobSelection'); + }, + + async assertJobSelectionNotExists() { + await testSubjects.missingOrFail('mlCalendarJobSelection'); + }, + + async assertJobSelection(expectedIdentifier: string[]) { + const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( + 'mlCalendarJobSelection > comboBoxInput' + ); + expect(comboBoxSelectedOptions).to.eql( + expectedIdentifier, + `Expected job selection to be '${expectedIdentifier}' (got '${comboBoxSelectedOptions}')` + ); + }, + + async assertJobSelectionContain(expectedIdentifier: string) { + const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( + 'mlCalendarJobSelection > comboBoxInput' + ); + expect(comboBoxSelectedOptions).to.contain( + expectedIdentifier, + `Expected job selection to contain '${expectedIdentifier}' (got '${comboBoxSelectedOptions}')` + ); + }, + + async selectJob(identifier: string) { + await comboBox.set('mlCalendarJobSelection > comboBoxInput', identifier); + await this.assertJobSelectionContain(identifier); + }, + + async assertJobGroupSelectionExists() { + await testSubjects.existOrFail('mlCalendarJobGroupSelection'); + }, + + async assertJobGroupSelectionNotExists() { + await testSubjects.missingOrFail('mlCalendarJobGroupSelection'); + }, + + async assertJobGroupSelection(expectedIdentifier: string[]) { + const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( + 'mlCalendarJobGroupSelection > comboBoxInput' + ); + expect(comboBoxSelectedOptions).to.eql( + expectedIdentifier, + `Expected job group selection to be'${expectedIdentifier}' (got '${comboBoxSelectedOptions}')` + ); + }, + + async assertJobGroupSelectionContain(expectedIdentifier: string) { + const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( + 'mlCalendarJobGroupSelection > comboBoxInput' + ); + expect(comboBoxSelectedOptions).to.contain( + expectedIdentifier, + `Expected job group selection to contain'${expectedIdentifier}' (got '${comboBoxSelectedOptions}')` + ); + }, + + async selectJobGroup(identifier: string) { + await comboBox.set('mlCalendarJobGroupSelection > comboBoxInput', identifier); + await this.assertJobGroupSelectionContain(identifier); + }, + + async deleteCalendarEventRow(eventDescription: string) { + await this.assertEventRowExists(eventDescription); + await testSubjects.click( + this.eventRowSelector(eventDescription, 'mlCalendarEventDeleteButton') + ); + await this.assertEventRowMissing(eventDescription); + }, + + async deleteCalendar() { + await this.assertDeleteCalendarButtonEnabled(true); + await testSubjects.click('mlCalendarButtonDelete'); + await testSubjects.existOrFail('mlCalendarDeleteConfirmation'); + await testSubjects.existOrFail('confirmModalConfirmButton'); + await testSubjects.click('confirmModalConfirmButton'); + await testSubjects.missingOrFail('mlCalendarDeleteConfirmation'); + }, }; } diff --git a/x-pack/test/functional/services/ml/settings_filter_list.ts b/x-pack/test/functional/services/ml/settings_filter_list.ts index 0afe9f21b03a6..bcac575b65c08 100644 --- a/x-pack/test/functional/services/ml/settings_filter_list.ts +++ b/x-pack/test/functional/services/ml/settings_filter_list.ts @@ -7,9 +7,14 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { MlCommonUI } from './common_ui'; -export function MachineLearningSettingsFilterListProvider({ getService }: FtrProviderContext) { +export function MachineLearningSettingsFilterListProvider( + { getService }: FtrProviderContext, + mlCommonUI: MlCommonUI +) { const testSubjects = getService('testSubjects'); + const browser = getService('browser'); return { async parseFilterListTable() { @@ -17,7 +22,7 @@ export function MachineLearningSettingsFilterListProvider({ getService }: FtrPro const $ = await table.parseDomContent(); const rows = []; - for (const tr of $.findTestSubjects('~mlFilterListsRow').toArray()) { + for (const tr of $.findTestSubjects('~mlFilterListRow').toArray()) { const $tr = $(tr); const inUseSubject = $tr @@ -55,6 +60,14 @@ export function MachineLearningSettingsFilterListProvider({ getService }: FtrPro return !subSelector ? row : `${row} > ${subSelector}`; }, + async assertFilterListRowExists(filterId: string) { + return await testSubjects.existOrFail(this.rowSelector(filterId)); + }, + + async assertFilterListRowNotExists(filterId: string) { + return await testSubjects.missingOrFail(this.rowSelector(filterId)); + }, + async filterWithSearchString(filter: string, expectedRowCount: number = 1) { const tableListContainer = await testSubjects.find('mlFilterListTableContainer'); const searchBarInput = await tableListContainer.findByClassName('euiFieldSearch'); @@ -101,6 +114,12 @@ export function MachineLearningSettingsFilterListProvider({ getService }: FtrPro await this.assertFilterListRowSelected(filterId, false); }, + async selectFilterListRowEditLink(filterId: string) { + await this.assertFilterListRowExists(filterId); + await testSubjects.click(this.rowSelector(filterId, `mlEditFilterListLink`)); + await testSubjects.existOrFail('mlPageFilterListEdit'); + }, + async assertCreateFilterListButtonEnabled(expectedValue: boolean) { const isEnabled = await testSubjects.isEnabled('mlFilterListsButtonCreate'); expect(isEnabled).to.eql( @@ -111,6 +130,10 @@ export function MachineLearningSettingsFilterListProvider({ getService }: FtrPro ); }, + async assertDeleteFilterListButtonExists() { + await testSubjects.existOrFail('mlFilterListsDeleteButton'); + }, + async assertDeleteFilterListButtonEnabled(expectedValue: boolean) { const isEnabled = await testSubjects.isEnabled('mlFilterListsDeleteButton'); expect(isEnabled).to.eql( @@ -121,6 +144,16 @@ export function MachineLearningSettingsFilterListProvider({ getService }: FtrPro ); }, + async deleteFilterList() { + await this.assertDeleteFilterListButtonExists(); + await this.assertDeleteFilterListButtonEnabled(true); + await testSubjects.click('mlFilterListsDeleteButton'); + await testSubjects.existOrFail('mlFilterListsDeleteButton'); + await testSubjects.existOrFail('mlFilterListDeleteConfirmation'); + await testSubjects.click('confirmModalConfirmButton'); + await testSubjects.missingOrFail('mlFilterListDeleteConfirmation'); + }, + async openFilterListEditForm(filterId: string) { await testSubjects.click(this.rowSelector(filterId, 'mlEditFilterListLink')); await testSubjects.existOrFail('mlPageFilterListEdit'); @@ -136,8 +169,8 @@ export function MachineLearningSettingsFilterListProvider({ getService }: FtrPro ); }, - async assertAddItemButtonEnabled(expectedValue: boolean) { - const isEnabled = await testSubjects.isEnabled('mlFilterListAddItemButton'); + async assertOpenNewItemsPopoverButtonEnabled(expectedValue: boolean) { + const isEnabled = await testSubjects.isEnabled('mlFilterListOpenNewItemsPopoverButton'); expect(isEnabled).to.eql( expectedValue, `Expected "add item" button to be '${expectedValue ? 'enabled' : 'disabled'}' (got '${ @@ -146,6 +179,16 @@ export function MachineLearningSettingsFilterListProvider({ getService }: FtrPro ); }, + async assertAddItemsButtonEnabled(expectedValue: boolean) { + const isEnabled = await testSubjects.isEnabled('mlFilterListOpenNewItemsPopoverButton'); + expect(isEnabled).to.eql( + expectedValue, + `Expected "add" button to be '${expectedValue ? 'enabled' : 'disabled'}' (got '${ + isEnabled ? 'enabled' : 'disabled' + }')` + ); + }, + async assertDeleteItemButtonEnabled(expectedValue: boolean) { const isEnabled = await testSubjects.isEnabled('mlFilterListDeleteItemButton'); expect(isEnabled).to.eql( @@ -156,11 +199,25 @@ export function MachineLearningSettingsFilterListProvider({ getService }: FtrPro ); }, + async assertSaveFilterListButtonEnabled(expectedValue: boolean) { + const isEnabled = await testSubjects.isEnabled('mlFilterListSaveButton'); + expect(isEnabled).to.eql( + expectedValue, + `Expected "save filter list" button to be '${ + expectedValue ? 'enabled' : 'disabled' + }' (got '${isEnabled ? 'enabled' : 'disabled'}')` + ); + }, + filterItemSelector(filterItem: string, subSelector?: string) { const row = `mlGridItem ${filterItem}`; return !subSelector ? row : `${row} > ${subSelector}`; }, + async assertFilterItemNotExists(filterItem: string) { + await testSubjects.missingOrFail(this.filterItemSelector(filterItem)); + }, + async assertFilterItemExists(filterItem: string) { await testSubjects.existOrFail(this.filterItemSelector(filterItem)); }, @@ -189,6 +246,13 @@ export function MachineLearningSettingsFilterListProvider({ getService }: FtrPro await this.assertFilterItemSelected(filterItem, true); }, + async deleteFilterItem(filterItem: string) { + await testSubjects.existOrFail('mlFilterListDeleteItemButton'); + await this.selectFilterItem(filterItem); + await testSubjects.click('mlFilterListDeleteItemButton'); + await this.assertFilterItemNotExists(filterItem); + }, + async deselectFilterItem(filterItem: string) { if ((await this.isFilterItemSelected(filterItem)) === true) { await testSubjects.click(this.filterItemSelector(filterItem)); @@ -196,5 +260,70 @@ export function MachineLearningSettingsFilterListProvider({ getService }: FtrPro await this.assertFilterItemSelected(filterItem, false); }, + + async navigateToFilterListCreationPage() { + await this.assertCreateFilterListButtonEnabled(true); + await testSubjects.click('mlFilterListsButtonCreate'); + await testSubjects.existOrFail('mlPageFilterListEdit'); + }, + + async assertFilterListIdValue(expectedValue: string) { + const subj = 'mlNewFilterListIdInput'; + const actualFilterListId = await testSubjects.getAttribute(subj, 'value'); + expect(actualFilterListId).to.eql( + expectedValue, + `Filter list id should be '${expectedValue}' (got '${actualFilterListId}')` + ); + }, + + async setFilterListId(filterId: string) { + const subj = 'mlNewFilterListIdInput'; + await mlCommonUI.setValueWithChecks(subj, filterId, { + clearWithKeyboard: true, + }); + await this.assertFilterListIdValue(filterId); + }, + + async setFilterListDescription(description: string) { + await this.assertEditDescriptionButtonEnabled(true); + await testSubjects.click('mlFilterListEditDescriptionButton'); + await testSubjects.existOrFail('mlFilterListDescriptionInput'); + await mlCommonUI.setValueWithChecks('mlFilterListDescriptionInput', description, { + clearWithKeyboard: true, + }); + await browser.pressKeys(browser.keys.ESCAPE); + await this.assertFilterListDescriptionValue(description); + }, + + async addFilterListKeywords(keywords: string[]) { + await this.assertOpenNewItemsPopoverButtonEnabled(true); + await testSubjects.click('mlFilterListOpenNewItemsPopoverButton'); + await mlCommonUI.setValueWithChecks('mlFilterListAddItemTextArea', keywords.join('\n'), { + clearWithKeyboard: true, + }); + await testSubjects.existOrFail('mlFilterListAddItemsButton'); + await this.assertAddItemsButtonEnabled(true); + await testSubjects.click('mlFilterListAddItemsButton'); + + for (let index = 0; index < keywords.length; index++) { + await this.assertFilterItemExists(keywords[index]); + } + }, + + async assertFilterListDescriptionValue(expectedDescription: string) { + const actualFilterListDescription = await testSubjects.getVisibleText( + 'mlNewFilterListDescriptionText' + ); + expect(actualFilterListDescription).to.eql( + expectedDescription, + `Filter list description should be '${expectedDescription}' (got '${actualFilterListDescription}')` + ); + }, + + async saveFilterList() { + await this.assertSaveFilterListButtonEnabled(true); + await testSubjects.click('mlFilterListSaveButton'); + await testSubjects.existOrFail('mlPageFilterListManagement'); + }, }; } diff --git a/x-pack/test/functional/services/monitoring/elasticsearch_nodes.js b/x-pack/test/functional/services/monitoring/elasticsearch_nodes.js index 0cae469e01697..55c34615373a9 100644 --- a/x-pack/test/functional/services/monitoring/elasticsearch_nodes.js +++ b/x-pack/test/functional/services/monitoring/elasticsearch_nodes.js @@ -6,6 +6,10 @@ import { range } from 'lodash'; +function trimAll(data) { + return data.map((item) => item.trim()); +} + export function MonitoringElasticsearchNodesProvider({ getService, getPageObjects }) { const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects(['monitoring']); @@ -35,6 +39,9 @@ export function MonitoringElasticsearchNodesProvider({ getService, getPageObject const SUBJ_NODES_DISKS = `${SUBJ_TABLE_BODY} > diskFreeSpace`; const SUBJ_NODES_SHARDS = `${SUBJ_TABLE_BODY} > shards`; + const SUBJ_NODES_ICON_PREFIX = `monitoringCellIcon`; + const SUBJ_NODES_POPOVER_PREFIX = `monitoringCellPopover`; + const SUBJ_NODE_LINK_PREFIX = `${SUBJ_TABLE_BODY} > nodeLink-`; return new (class ElasticsearchIndices { @@ -77,7 +84,6 @@ export function MonitoringElasticsearchNodesProvider({ getService, getPageObject await find.clickByCssSelector(`[data-test-subj="${SUBJ_TABLE_SORT_MEM_COL}"] > button`); await this.waitForTableToFinishLoading(); } - async clickDiskCol() { await find.clickByCssSelector(`[data-test-subj="${SUBJ_TABLE_SORT_DISK_COL}"] > button`); } @@ -108,12 +114,35 @@ export function MonitoringElasticsearchNodesProvider({ getService, getPageObject async getNodesAll() { const names = await testSubjects.getVisibleTextAll(SUBJ_NODES_NAMES); const statuses = await testSubjects.getAttributeAll(SUBJ_NODES_STATUSES, 'alt'); - const cpus = await testSubjects.getVisibleTextAll(SUBJ_NODES_CPUS); - const loads = await testSubjects.getVisibleTextAll(SUBJ_NODES_LOADS); - const memories = await testSubjects.getVisibleTextAll(SUBJ_NODES_MEMS); - const disks = await testSubjects.getVisibleTextAll(SUBJ_NODES_DISKS); + const cpus = trimAll(await testSubjects.getVisibleTextAll(SUBJ_NODES_CPUS)); + const loads = trimAll(await testSubjects.getVisibleTextAll(SUBJ_NODES_LOADS)); + const memories = trimAll(await testSubjects.getVisibleTextAll(SUBJ_NODES_MEMS)); + const disks = trimAll(await testSubjects.getVisibleTextAll(SUBJ_NODES_DISKS)); const shards = await testSubjects.getVisibleTextAll(SUBJ_NODES_SHARDS); + const areasWithText = { + cpuUsage: [], + loadAverage: [], + jvmMemory: [], + diskFreeSpace: [], + }; + + const table = await testSubjects.find(SUBJ_TABLE_BODY); + for (const key of Object.keys(areasWithText)) { + const text = areasWithText[key]; + const icons = await testSubjects.findAllDescendant( + `${SUBJ_NODES_ICON_PREFIX}-${key}`, + table + ); + for (const icon of icons) { + await icon.moveMouseTo(); + await icon.click(); + const _text = await testSubjects.getVisibleTextAll(`${SUBJ_NODES_POPOVER_PREFIX}-${key}`); + text.push(_text[0]); + await icon.click(); + } + } + // tuple-ize the icons and texts together into an array of objects const tableRows = await this.getRows(); const iterator = range(tableRows.length); @@ -124,9 +153,13 @@ export function MonitoringElasticsearchNodesProvider({ getService, getPageObject name: names[current], status: statuses[current], cpu: cpus[current], + cpuText: areasWithText.cpuUsage[current], load: loads[current], + loadText: areasWithText.loadAverage[current], memory: memories[current], + memoryText: areasWithText.jvmMemory[current], disk: disks[current], + diskText: areasWithText.diskFreeSpace[current], shards: shards[current], }, ]; diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts index 67672cb54c21b..359be662b0216 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts @@ -40,8 +40,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { return createdAlert; } - // FLAKY: https://github.com/elastic/kibana/issues/77401 - describe.skip('alerts', function () { + describe('alerts', function () { before(async () => { await pageObjects.common.navigateToApp('triggersActions'); await testSubjects.click('alertsTab'); @@ -385,9 +384,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await testSubjects.click('deleteAll'); await testSubjects.existOrFail('deleteIdsConfirmation'); await testSubjects.click('deleteIdsConfirmation > confirmModalConfirmButton'); - await testSubjects.missingOrFail('deleteIdsConfirmation', { timeout: 5000 }); + await testSubjects.missingOrFail('deleteIdsConfirmation'); - await pageObjects.common.closeToast(); + await retry.try(async () => { + const toastTitle = await pageObjects.common.closeToast(); + expect(toastTitle).to.eql('Deleted 10 alerts'); + }); await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(namePrefix); diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts index 86e355988da0b..151c837640228 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts @@ -17,6 +17,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const testSubjects = getService('testSubjects'); const pageObjects = getPageObjects(['common', 'triggersActionsUI', 'header']); const find = getService('find'); + const retry = getService('retry'); + const comboBox = getService('comboBox'); describe('Connectors', function () { before(async () => { @@ -76,7 +78,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await testSubjects.setValue('slackWebhookUrlInput', 'https://test'); - await find.clickByCssSelector('[data-test-subj="saveEditedActionButton"]:not(disabled)'); + await find.clickByCssSelector( + '[data-test-subj="saveAndCloseEditedActionButton"]:not(disabled)' + ); const toastTitle = await pageObjects.common.closeToast(); expect(toastTitle).to.eql(`Updated '${updatedConnectorName}'`); @@ -92,6 +96,64 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { ]); }); + it('should test a connector and display a successful result', async () => { + const connectorName = generateUniqueKey(); + const indexName = generateUniqueKey(); + await createIndexConnector(connectorName, indexName); + + await pageObjects.triggersActionsUI.searchConnectors(connectorName); + + const searchResultsBeforeEdit = await pageObjects.triggersActionsUI.getConnectorsList(); + expect(searchResultsBeforeEdit.length).to.eql(1); + + await find.clickByCssSelector('[data-test-subj="connectorsTableCell-name"] button'); + + await find.clickByCssSelector('[data-test-subj="testConnectorTab"]'); + + // test success + await testSubjects.setValue('documentsJsonEditor', '{ "key": "value" }'); + + await find.clickByCssSelector('[data-test-subj="executeActionButton"]:not(disabled)'); + + await retry.try(async () => { + await testSubjects.find('executionSuccessfulResult'); + }); + + await find.clickByCssSelector( + '[data-test-subj="cancelSaveEditedConnectorButton"]:not(disabled)' + ); + }); + + it('should test a connector and display a failure result', async () => { + const connectorName = generateUniqueKey(); + const indexName = generateUniqueKey(); + await createIndexConnector(connectorName, indexName); + + await pageObjects.triggersActionsUI.searchConnectors(connectorName); + + const searchResultsBeforeEdit = await pageObjects.triggersActionsUI.getConnectorsList(); + expect(searchResultsBeforeEdit.length).to.eql(1); + + await find.clickByCssSelector('[data-test-subj="connectorsTableCell-name"] button'); + + await find.clickByCssSelector('[data-test-subj="testConnectorTab"]'); + + await testSubjects.setValue('documentsJsonEditor', '{ "": "value" }'); + + await find.clickByCssSelector('[data-test-subj="executeActionButton"]:not(disabled)'); + + await retry.try(async () => { + const executionFailureResultCallout = await testSubjects.find('executionFailureResult'); + expect(await executionFailureResultCallout.getVisibleText()).to.match( + /error indexing documents/ + ); + }); + + await find.clickByCssSelector( + '[data-test-subj="cancelSaveEditedConnectorButton"]:not(disabled)' + ); + }); + it('should reset connector when canceling an edit', async () => { const connectorName = generateUniqueKey(); await createConnector(connectorName); @@ -193,7 +255,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await find.clickByCssSelector('[data-test-subj="connectorsTableCell-name"] button'); expect(await testSubjects.exists('preconfiguredBadge')).to.be(true); - expect(await testSubjects.exists('saveEditedActionButton')).to.be(false); + expect(await testSubjects.exists('saveAndCloseEditedActionButton')).to.be(false); }); }); @@ -209,4 +271,17 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await find.clickByCssSelector('[data-test-subj="saveNewActionButton"]:not(disabled)'); await pageObjects.common.closeToast(); } + + async function createIndexConnector(connectorName: string, indexName: string) { + await pageObjects.triggersActionsUI.clickCreateConnectorButton(); + + await testSubjects.click('.index-card'); + + await testSubjects.setValue('nameInput', connectorName); + + await comboBox.set('connectorIndexesComboBox', indexName); + + await find.clickByCssSelector('[data-test-subj="saveNewActionButton"]:not(disabled)'); + await pageObjects.common.closeToast(); + } }; diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/bulk_upgrade.ts b/x-pack/test/ingest_manager_api_integration/apis/epm/bulk_upgrade.ts new file mode 100644 index 0000000000000..e377ea5a762f9 --- /dev/null +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/bulk_upgrade.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { skipIfNoDockerRegistry } from '../../helpers'; +import { + BulkInstallPackageInfo, + BulkInstallPackagesResponse, + IBulkInstallPackageError, +} from '../../../../plugins/ingest_manager/common'; + +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + const supertest = getService('supertest'); + + const deletePackage = async (pkgkey: string) => { + await supertest.delete(`/api/ingest_manager/epm/packages/${pkgkey}`).set('kbn-xsrf', 'xxxx'); + }; + + describe('bulk package upgrade api', async () => { + skipIfNoDockerRegistry(providerContext); + + describe('bulk package upgrade with a package already installed', async () => { + beforeEach(async () => { + await supertest + .post(`/api/ingest_manager/epm/packages/multiple_versions-0.1.0`) + .set('kbn-xsrf', 'xxxx') + .send({ force: true }) + .expect(200); + }); + afterEach(async () => { + await deletePackage('multiple_versions-0.1.0'); + await deletePackage('multiple_versions-0.3.0'); + await deletePackage('overrides-0.1.0'); + }); + + it('should return 400 if no packages are requested for upgrade', async function () { + await supertest + .post(`/api/ingest_manager/epm/packages/_bulk`) + .set('kbn-xsrf', 'xxxx') + .expect(400); + }); + it('should return 200 and an array for upgrading a package', async function () { + const { body }: { body: BulkInstallPackagesResponse } = await supertest + .post(`/api/ingest_manager/epm/packages/_bulk`) + .set('kbn-xsrf', 'xxxx') + .send({ packages: ['multiple_versions'] }) + .expect(200); + expect(body.response.length).equal(1); + expect(body.response[0].name).equal('multiple_versions'); + const entry = body.response[0] as BulkInstallPackageInfo; + expect(entry.oldVersion).equal('0.1.0'); + expect(entry.newVersion).equal('0.3.0'); + }); + it('should return an error for packages that do not exist', async function () { + const { body }: { body: BulkInstallPackagesResponse } = await supertest + .post(`/api/ingest_manager/epm/packages/_bulk`) + .set('kbn-xsrf', 'xxxx') + .send({ packages: ['multiple_versions', 'blahblah'] }) + .expect(200); + expect(body.response.length).equal(2); + expect(body.response[0].name).equal('multiple_versions'); + const entry = body.response[0] as BulkInstallPackageInfo; + expect(entry.oldVersion).equal('0.1.0'); + expect(entry.newVersion).equal('0.3.0'); + + const err = body.response[1] as IBulkInstallPackageError; + expect(err.statusCode).equal(404); + expect(body.response[1].name).equal('blahblah'); + }); + it('should upgrade multiple packages', async function () { + const { body }: { body: BulkInstallPackagesResponse } = await supertest + .post(`/api/ingest_manager/epm/packages/_bulk`) + .set('kbn-xsrf', 'xxxx') + .send({ packages: ['multiple_versions', 'overrides'] }) + .expect(200); + expect(body.response.length).equal(2); + expect(body.response[0].name).equal('multiple_versions'); + let entry = body.response[0] as BulkInstallPackageInfo; + expect(entry.oldVersion).equal('0.1.0'); + expect(entry.newVersion).equal('0.3.0'); + + entry = body.response[1] as BulkInstallPackageInfo; + expect(entry.oldVersion).equal(null); + expect(entry.newVersion).equal('0.1.0'); + expect(entry.name).equal('overrides'); + }); + }); + + describe('bulk upgrade without package already installed', async () => { + afterEach(async () => { + await deletePackage('multiple_versions-0.3.0'); + }); + + it('should return 200 and an array for upgrading a package', async function () { + const { body }: { body: BulkInstallPackagesResponse } = await supertest + .post(`/api/ingest_manager/epm/packages/_bulk`) + .set('kbn-xsrf', 'xxxx') + .send({ packages: ['multiple_versions'] }) + .expect(200); + expect(body.response.length).equal(1); + expect(body.response[0].name).equal('multiple_versions'); + const entry = body.response[0] as BulkInstallPackageInfo; + expect(entry.oldVersion).equal(null); + expect(entry.newVersion).equal('0.3.0'); + }); + }); + }); +} diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/index.js b/x-pack/test/ingest_manager_api_integration/apis/epm/index.js index 28743ee5f43c2..e509babc9828b 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/epm/index.js +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/index.js @@ -16,6 +16,7 @@ export default function loadTests({ loadTestFile }) { loadTestFile(require.resolve('./install_prerelease')); loadTestFile(require.resolve('./install_remove_assets')); loadTestFile(require.resolve('./install_update')); + loadTestFile(require.resolve('./bulk_upgrade')); loadTestFile(require.resolve('./update_assets')); loadTestFile(require.resolve('./data_stream')); loadTestFile(require.resolve('./package_install_complete')); diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/install_remove_assets.ts b/x-pack/test/ingest_manager_api_integration/apis/epm/install_remove_assets.ts index 198c129b7482f..492af399d5e30 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/epm/install_remove_assets.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/install_remove_assets.ts @@ -29,7 +29,8 @@ export default function (providerContext: FtrProviderContext) { .send({ force: true }); }; - describe('installs and uninstalls all assets', async () => { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/72102 + describe.skip('installs and uninstalls all assets', async () => { describe('installs all assets when installing a package for the first time', async () => { skipIfNoDockerRegistry(providerContext); before(async () => { diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/update_assets.ts b/x-pack/test/ingest_manager_api_integration/apis/epm/update_assets.ts index 9af27f5f98558..8203b4d183871 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/epm/update_assets.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/update_assets.ts @@ -32,7 +32,8 @@ export default function (providerContext: FtrProviderContext) { .send({ force: true }); }; - describe('updates all assets when updating a package to a different version', async () => { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/72102 + describe.skip('updates all assets when updating a package to a different version', async () => { skipIfNoDockerRegistry(providerContext); before(async () => { await installPackage(pkgKey); diff --git a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/acks.ts b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/acks.ts index 1613ca9d11ee6..360b91203dfc8 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/acks.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/acks.ts @@ -13,6 +13,7 @@ export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; const esArchiver = getService('esArchiver'); const esClient = getService('es'); + const kibanaServer = getService('kibanaServer'); const supertest = getSupertestWithoutAuth(providerContext); let apiKey: { id: string; api_key: string }; @@ -205,5 +206,50 @@ export default function (providerContext: FtrProviderContext) { 'ACTION not allowed for acknowledgment only ACTION_RESULT' ); }); + + it('ack upgrade should update fleet-agent SO', async () => { + const { body: actionRes } = await supertest + .post(`/api/ingest_manager/fleet/agents/agent1/actions`) + .set('kbn-xsrf', 'xx') + .set( + 'Authorization', + `ApiKey ${Buffer.from(`${apiKey.id}:${apiKey.api_key}`).toString('base64')}` + ) + .send({ + action: { + type: 'UPGRADE', + ack_data: { version: '8.0.0', source_uri: 'http://localhost:8000' }, + }, + }) + .expect(200); + const actionId = actionRes.item.id; + await supertest + .post(`/api/ingest_manager/fleet/agents/agent1/acks`) + .set('kbn-xsrf', 'xx') + .set( + 'Authorization', + `ApiKey ${Buffer.from(`${apiKey.id}:${apiKey.api_key}`).toString('base64')}` + ) + .send({ + events: [ + { + type: 'ACTION_RESULT', + subtype: 'ACKNOWLEDGED', + timestamp: '2020-09-21T13:25:29.02838-04:00', + action_id: actionId, + agent_id: 'agent1', + message: + "Action '70d97288-ffd9-4549-8c49-2423a844f67f' of type 'UPGRADE' acknowledged.", + }, + ], + }) + .expect(200); + + const res = await kibanaServer.savedObjects.get({ + type: 'fleet-agents', + id: 'agent1', + }); + expect(res.attributes.upgraded_at).to.be.ok(); + }); }); } diff --git a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/reassign.ts b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/reassign.ts new file mode 100644 index 0000000000000..f3e24fab1dc2a --- /dev/null +++ b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/reassign.ts @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../api_integration/ftr_provider_context'; +import { setupIngest } from './services'; + +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + + describe('fleet_reassign_agent', () => { + setupIngest(providerContext); + before(async () => { + await esArchiver.loadIfNeeded('fleet/agents'); + }); + after(async () => { + await esArchiver.unload('fleet/agents'); + }); + + it('should allow to reassign single agent', async () => { + await supertest + .put(`/api/ingest_manager/fleet/agents/agent1/reassign`) + .set('kbn-xsrf', 'xxx') + .send({ + policy_id: 'policy2', + }) + .expect(200); + const { body } = await supertest + .get(`/api/ingest_manager/fleet/agents/agent1`) + .set('kbn-xsrf', 'xxx'); + expect(body.item.policy_id).to.eql('policy2'); + }); + + it('should throw an error for invalid policy id for single reassign', async () => { + await supertest + .put(`/api/ingest_manager/fleet/agents/agent1/reassign`) + .set('kbn-xsrf', 'xxx') + .send({ + policy_id: 'INVALID_ID', + }) + .expect(404); + }); + + it('should allow to reassign multiple agents by id', async () => { + await supertest + .post(`/api/ingest_manager/fleet/agents/bulk_reassign`) + .set('kbn-xsrf', 'xxx') + .send({ + agents: ['agent2', 'agent3'], + policy_id: 'policy2', + }) + .expect(200); + const [agent2data, agent3data] = await Promise.all([ + supertest.get(`/api/ingest_manager/fleet/agents/agent2`).set('kbn-xsrf', 'xxx'), + supertest.get(`/api/ingest_manager/fleet/agents/agent3`).set('kbn-xsrf', 'xxx'), + ]); + expect(agent2data.body.item.policy_id).to.eql('policy2'); + expect(agent3data.body.item.policy_id).to.eql('policy2'); + }); + + it('should allow to reassign multiple agents by kuery', async () => { + await supertest + .post(`/api/ingest_manager/fleet/agents/bulk_reassign`) + .set('kbn-xsrf', 'xxx') + .send({ + agents: 'fleet-agents.active: true', + policy_id: 'policy2', + }) + .expect(200); + const { body } = await supertest + .get(`/api/ingest_manager/fleet/agents`) + .set('kbn-xsrf', 'xxx'); + expect(body.total).to.eql(4); + body.list.forEach((agent: any) => { + expect(agent.policy_id).to.eql('policy2'); + }); + }); + + it('should throw an error for invalid policy id for bulk reassign', async () => { + await supertest + .post(`/api/ingest_manager/fleet/agents/bulk_reassign`) + .set('kbn-xsrf', 'xxx') + .send({ + agents: ['agent2', 'agent3'], + policy_id: 'INVALID_ID', + }) + .expect(404); + }); + }); +} diff --git a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/unenroll.ts b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/unenroll.ts index d24e438fa13ea..ce5dfd7714ab2 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/unenroll.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/unenroll.ts @@ -64,7 +64,7 @@ export default function (providerContext: FtrProviderContext) { await esArchiver.unload('fleet/agents'); }); - it('allow to unenroll using a list of ids', async () => { + it('should allow to unenroll single agent', async () => { await supertest .post(`/api/ingest_manager/fleet/agents/agent1/unenroll`) .set('kbn-xsrf', 'xxx') @@ -95,5 +95,39 @@ export default function (providerContext: FtrProviderContext) { expect(outputAPIKeys).length(1); expect(outputAPIKeys[0].invalidated).eql(true); }); + + it('should allow to unenroll multiple agents by id', async () => { + await supertest + .post(`/api/ingest_manager/fleet/agents/bulk_unenroll`) + .set('kbn-xsrf', 'xxx') + .send({ + agents: ['agent2', 'agent3'], + }) + .expect(200); + const [agent2data, agent3data] = await Promise.all([ + supertest.get(`/api/ingest_manager/fleet/agents/agent2`).set('kbn-xsrf', 'xxx'), + supertest.get(`/api/ingest_manager/fleet/agents/agent3`).set('kbn-xsrf', 'xxx'), + ]); + expect(typeof agent2data.body.item.unenrollment_started_at).to.eql('string'); + expect(agent2data.body.item.active).to.eql(true); + expect(typeof agent3data.body.item.unenrollment_started_at).to.be('string'); + expect(agent2data.body.item.active).to.eql(true); + }); + + it('should allow to unenroll multiple agents by kuery', async () => { + await supertest + .post(`/api/ingest_manager/fleet/agents/bulk_unenroll`) + .set('kbn-xsrf', 'xxx') + .send({ + agents: 'fleet-agents.active: true', + force: true, + }) + .expect(200); + + const { body } = await supertest + .get(`/api/ingest_manager/fleet/agents`) + .set('kbn-xsrf', 'xxx'); + expect(body.total).to.eql(0); + }); }); } diff --git a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/upgrade.ts b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/upgrade.ts new file mode 100644 index 0000000000000..a783f806c03ee --- /dev/null +++ b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/upgrade.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect/expect.js'; +import { FtrProviderContext } from '../../../../api_integration/ftr_provider_context'; +import { setupIngest } from './services'; +import { skipIfNoDockerRegistry } from '../../../helpers'; + +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + + describe('fleet upgrade agent', () => { + skipIfNoDockerRegistry(providerContext); + setupIngest(providerContext); + before(async () => { + await esArchiver.loadIfNeeded('fleet/agents'); + }); + after(async () => { + await esArchiver.unload('fleet/agents'); + }); + + it('should respond 200 to upgrade agent and update the agent SO', async () => { + const kibanaVersionAccessor = kibanaServer.version; + const kibanaVersion = await kibanaVersionAccessor.get(); + await supertest + .post(`/api/ingest_manager/fleet/agents/agent1/upgrade`) + .set('kbn-xsrf', 'xxx') + .send({ + version: kibanaVersion, + source_uri: 'http://path/to/download', + }) + .expect(200); + const res = await kibanaServer.savedObjects.get({ + type: 'fleet-agents', + id: 'agent1', + }); + expect(res.attributes.upgrade_started_at).to.be.ok(); + }); + + it('should respond 400 if trying to upgrade to a version that does not match installed kibana version', async () => { + await supertest + .post(`/api/ingest_manager/fleet/agents/agent1/upgrade`) + .set('kbn-xsrf', 'xxx') + .send({ + version: '8.0.1', + source_uri: 'http://path/to/download', + }) + .expect(400); + }); + }); +} diff --git a/x-pack/test/ingest_manager_api_integration/apis/fleet/enrollment_api_keys/crud.ts b/x-pack/test/ingest_manager_api_integration/apis/fleet/enrollment_api_keys/crud.ts index 6c5d552a51eb9..b3519e0ccc2a3 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/fleet/enrollment_api_keys/crud.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/fleet/enrollment_api_keys/crud.ts @@ -36,7 +36,7 @@ export default function (providerContext: FtrProviderContext) { .get(`/api/ingest_manager/fleet/enrollment-api-keys`) .expect(200); - expect(apiResponse.total).to.be(2); + expect(apiResponse.total).to.be(3); expect(apiResponse.list[0]).to.have.keys('id', 'api_key_id', 'name'); }); }); diff --git a/x-pack/test/ingest_manager_api_integration/apis/fleet/index.js b/x-pack/test/ingest_manager_api_integration/apis/fleet/index.js index 3a72fe6d9f12b..9eb51bee3c7dd 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/fleet/index.js +++ b/x-pack/test/ingest_manager_api_integration/apis/fleet/index.js @@ -18,5 +18,7 @@ export default function loadTests({ loadTestFile }) { loadTestFile(require.resolve('./enrollment_api_keys/crud')); loadTestFile(require.resolve('./install')); loadTestFile(require.resolve('./agents/actions')); + loadTestFile(require.resolve('./agents/upgrade')); + loadTestFile(require.resolve('./agents/reassign')); }); } diff --git a/x-pack/test/plugin_functional/plugins/resolver_test/public/applications/resolver_test/index.tsx b/x-pack/test/plugin_functional/plugins/resolver_test/public/applications/resolver_test/index.tsx index f3d1eb60bf1c0..d70d46fcbc015 100644 --- a/x-pack/test/plugin_functional/plugins/resolver_test/public/applications/resolver_test/index.tsx +++ b/x-pack/test/plugin_functional/plugins/resolver_test/public/applications/resolver_test/index.tsx @@ -61,12 +61,12 @@ const AppRoot = React.memo( storeFactory, ResolverWithoutProviders, mocks: { - dataAccessLayer: { noAncestorsTwoChildren }, + dataAccessLayer: { noAncestorsTwoChildrenWithRelatedEventsOnOrigin }, }, } = resolverPluginSetup; const dataAccessLayer: DataAccessLayer = useMemo( - () => noAncestorsTwoChildren().dataAccessLayer, - [noAncestorsTwoChildren] + () => noAncestorsTwoChildrenWithRelatedEventsOnOrigin().dataAccessLayer, + [noAncestorsTwoChildrenWithRelatedEventsOnOrigin] ); const store = useMemo(() => { diff --git a/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_cases.ts b/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_cases.ts index b32950538f8e5..190b12e038b27 100644 --- a/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_cases.ts +++ b/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_cases.ts @@ -4,30 +4,48 @@ * you may not use this file except in compliance with the Elastic License. */ -export const SAVED_OBJECT_TEST_CASES = Object.freeze({ +import { SPACES } from './spaces'; +import { TestCase } from './types'; + +const { + DEFAULT: { spaceId: DEFAULT_SPACE_ID }, + SPACE_1: { spaceId: SPACE_1_ID }, + SPACE_2: { spaceId: SPACE_2_ID }, +} = SPACES; +const EACH_SPACE = [DEFAULT_SPACE_ID, SPACE_1_ID, SPACE_2_ID]; + +type CommonTestCase = Omit & { originId?: string }; + +export const SAVED_OBJECT_TEST_CASES: Record = Object.freeze({ SINGLE_NAMESPACE_DEFAULT_SPACE: Object.freeze({ type: 'isolatedtype', id: 'defaultspace-isolatedtype-id', + expectedNamespaces: [DEFAULT_SPACE_ID], }), SINGLE_NAMESPACE_SPACE_1: Object.freeze({ type: 'isolatedtype', id: 'space1-isolatedtype-id', + expectedNamespaces: [SPACE_1_ID], }), SINGLE_NAMESPACE_SPACE_2: Object.freeze({ type: 'isolatedtype', id: 'space2-isolatedtype-id', + expectedNamespaces: [SPACE_2_ID], }), MULTI_NAMESPACE_DEFAULT_AND_SPACE_1: Object.freeze({ type: 'sharedtype', id: 'default_and_space_1', + expectedNamespaces: [DEFAULT_SPACE_ID, SPACE_1_ID], }), MULTI_NAMESPACE_ONLY_SPACE_1: Object.freeze({ type: 'sharedtype', id: 'only_space_1', + expectedNamespaces: [SPACE_1_ID], }), MULTI_NAMESPACE_ONLY_SPACE_2: Object.freeze({ type: 'sharedtype', id: 'only_space_2', + expectedNamespaces: [SPACE_2_ID], }), NAMESPACE_AGNOSTIC: Object.freeze({ type: 'globaltype', @@ -38,3 +56,37 @@ export const SAVED_OBJECT_TEST_CASES = Object.freeze({ id: 'any', }), }); + +/** + * These objects exist in the test data for all saved object test suites, but they are only used to test various conflict scenarios. + */ +export const CONFLICT_TEST_CASES: Record = Object.freeze({ + CONFLICT_1_OBJ: Object.freeze({ + type: 'sharedtype', + id: 'conflict_1', + expectedNamespaces: EACH_SPACE, + }), + CONFLICT_2A_OBJ: Object.freeze({ + type: 'sharedtype', + id: 'conflict_2a', + originId: 'conflict_2', + expectedNamespaces: EACH_SPACE, + }), + CONFLICT_2B_OBJ: Object.freeze({ + type: 'sharedtype', + id: 'conflict_2b', + originId: 'conflict_2', + expectedNamespaces: EACH_SPACE, + }), + CONFLICT_3_OBJ: Object.freeze({ + type: 'sharedtype', + id: 'conflict_3', + expectedNamespaces: EACH_SPACE, + }), + CONFLICT_4A_OBJ: Object.freeze({ + type: 'sharedtype', + id: 'conflict_4a', + originId: 'conflict_4', + expectedNamespaces: EACH_SPACE, + }), +}); diff --git a/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_utils.ts b/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_utils.ts index 595986c08efc1..9d4b5e80e9c3d 100644 --- a/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_utils.ts +++ b/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_utils.ts @@ -6,7 +6,6 @@ import expect from '@kbn/expect'; import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; -import { SAVED_OBJECT_TEST_CASES as CASES } from './saved_object_test_cases'; import { SPACES } from './spaces'; import { AUTHENTICATION } from './authentication'; import { TestCase, TestUser, ExpectResponseBody } from './types'; @@ -73,6 +72,28 @@ export const getTestTitle = ( return `${list.join(' and ')}`; }; +export const isUserAuthorizedAtSpace = (user: TestUser | undefined, namespace: string) => + !user || user.authorizedAtSpaces.includes('*') || user.authorizedAtSpaces.includes(namespace); + +export const getRedactedNamespaces = ( + user: TestUser | undefined, + namespaces: string[] | undefined +) => namespaces?.map((x) => (isUserAuthorizedAtSpace(user, x) ? x : '?')).sort(namespaceComparator); +function namespaceComparator(a: string, b: string) { + // namespaces get sorted so that they're all in alphabetical order, and unknown ones appear at the end + // this is to prevent information disclosure + if (a === '?' && b !== '?') { + return 1; + } else if (b === '?' && a !== '?') { + return -1; + } else if (a > b) { + return 1; + } else if (a < b) { + return -1; + } + return 0; +} + export const testCaseFailures = { // test suites need explicit return types for number primitives fail400: (condition?: boolean): { failure?: 400 } => @@ -150,7 +171,7 @@ export const expectResponses = { } }, /** - * Additional assertions that we use in `bulk_create` and `create` to ensure that + * Additional assertions that we use in `import` and `resolve_import_errors` to ensure that * newly-created (or overwritten) objects don't have unexpected properties */ successCreated: async (es: any, spaceId: string, type: string, id: string) => { @@ -161,26 +182,6 @@ export const expectResponses = { id: `${expectedSpacePrefix}${type}:${id}`, index: '.kibana', }); - const { namespace: actualNamespace, namespaces: actualNamespaces } = savedObject._source; - if (isNamespaceUndefined) { - expect(actualNamespace).to.eql(undefined); - } else { - expect(actualNamespace).to.eql(spaceId); - } - if (isMultiNamespace(type)) { - if (['conflict_1', 'conflict_2a', 'conflict_2b', 'conflict_3', 'conflict_4a'].includes(id)) { - expect(actualNamespaces).to.eql([DEFAULT_SPACE_ID, SPACE_1_ID, SPACE_2_ID]); - } else if (id === CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1.id) { - expect(actualNamespaces).to.eql([DEFAULT_SPACE_ID, SPACE_1_ID]); - } else if (id === CASES.MULTI_NAMESPACE_ONLY_SPACE_1.id) { - expect(actualNamespaces).to.eql([SPACE_1_ID]); - } else if (id === CASES.MULTI_NAMESPACE_ONLY_SPACE_2.id) { - expect(actualNamespaces).to.eql([SPACE_2_ID]); - } else { - // newly created in this space - expect(actualNamespaces).to.eql([spaceId]); - } - } return savedObject; }, }; diff --git a/x-pack/test/saved_object_api_integration/common/lib/types.ts b/x-pack/test/saved_object_api_integration/common/lib/types.ts index 56e6a992b6b62..b52a84f352999 100644 --- a/x-pack/test/saved_object_api_integration/common/lib/types.ts +++ b/x-pack/test/saved_object_api_integration/common/lib/types.ts @@ -21,6 +21,7 @@ export interface TestSuite { export interface TestCase { type: string; id: string; + expectedNamespaces?: string[]; failure?: 400 | 403 | 404 | 409; } diff --git a/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts b/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts index e3163ef77d427..b1608946b8e62 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts @@ -14,8 +14,9 @@ import { expectResponses, getUrlPrefix, getTestTitle, + getRedactedNamespaces, } from '../lib/saved_object_test_utils'; -import { ExpectResponseBody, TestCase, TestDefinition, TestSuite } from '../lib/types'; +import { ExpectResponseBody, TestCase, TestDefinition, TestSuite, TestUser } from '../lib/types'; export interface BulkCreateTestDefinition extends TestDefinition { request: Array<{ type: string; id: string }>; @@ -33,7 +34,7 @@ const NEW_ATTRIBUTE_VAL = `New attribute value ${Date.now()}`; const NEW_SINGLE_NAMESPACE_OBJ = Object.freeze({ type: 'dashboard', id: 'new-dashboard-id' }); const NEW_MULTI_NAMESPACE_OBJ = Object.freeze({ type: 'sharedtype', id: 'new-sharedtype-id' }); const NEW_NAMESPACE_AGNOSTIC_OBJ = Object.freeze({ type: 'globaltype', id: 'new-globaltype-id' }); -export const TEST_CASES = Object.freeze({ +export const TEST_CASES: Record = Object.freeze({ ...CASES, NEW_SINGLE_NAMESPACE_OBJ, NEW_MULTI_NAMESPACE_OBJ, @@ -45,7 +46,7 @@ export function bulkCreateTestSuiteFactory(es: any, esArchiver: any, supertest: const expectResponseBody = ( testCases: BulkCreateTestCase | BulkCreateTestCase[], statusCode: 200 | 403, - spaceId = SPACES.DEFAULT.spaceId + user?: TestUser ): ExpectResponseBody => async (response: Record) => { const testCaseArray = Array.isArray(testCases) ? testCases : [testCases]; if (statusCode === 403) { @@ -70,7 +71,8 @@ export function bulkCreateTestSuiteFactory(es: any, esArchiver: any, supertest: await expectResponses.permitted(object, testCase); if (!testCase.failure) { expect(object.attributes[NEW_ATTRIBUTE_KEY]).to.eql(NEW_ATTRIBUTE_VAL); - await expectResponses.successCreated(es, spaceId, object.type, object.id); + const redactedNamespaces = getRedactedNamespaces(user, testCase.expectedNamespaces); + expect(object.namespaces).to.eql(redactedNamespaces); } } } @@ -81,6 +83,7 @@ export function bulkCreateTestSuiteFactory(es: any, esArchiver: any, supertest: overwrite: boolean, options?: { spaceId?: string; + user?: TestUser; singleRequest?: boolean; responseBodyOverride?: ExpectResponseBody; } @@ -95,8 +98,7 @@ export function bulkCreateTestSuiteFactory(es: any, esArchiver: any, supertest: request: [createRequest(x)], responseStatusCode, responseBody: - options?.responseBodyOverride || - expectResponseBody(x, responseStatusCode, options?.spaceId), + options?.responseBodyOverride || expectResponseBody(x, responseStatusCode, options?.user), overwrite, })); } @@ -108,7 +110,7 @@ export function bulkCreateTestSuiteFactory(es: any, esArchiver: any, supertest: responseStatusCode, responseBody: options?.responseBodyOverride || - expectResponseBody(cases, responseStatusCode, options?.spaceId), + expectResponseBody(cases, responseStatusCode, options?.user), overwrite, }, ]; diff --git a/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts b/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts index 8de54fe499c07..71ece1265347c 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts @@ -25,7 +25,10 @@ export interface BulkGetTestCase extends TestCase { } const DOES_NOT_EXIST = Object.freeze({ type: 'dashboard', id: 'does-not-exist' }); -export const TEST_CASES = Object.freeze({ ...CASES, DOES_NOT_EXIST }); +export const TEST_CASES: Record = Object.freeze({ + ...CASES, + DOES_NOT_EXIST, +}); export function bulkGetTestSuiteFactory(esArchiver: any, supertest: SuperTest) { const expectForbidden = expectResponses.forbiddenTypes('bulk_get'); diff --git a/x-pack/test/saved_object_api_integration/common/suites/bulk_update.ts b/x-pack/test/saved_object_api_integration/common/suites/bulk_update.ts index 2e3c55f029d29..c3020b2da3219 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/bulk_update.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/bulk_update.ts @@ -24,7 +24,10 @@ const NEW_ATTRIBUTE_KEY = 'title'; // all type mappings include this attribute, const NEW_ATTRIBUTE_VAL = `Updated attribute value ${Date.now()}`; const DOES_NOT_EXIST = Object.freeze({ type: 'dashboard', id: 'does-not-exist' }); -export const TEST_CASES = Object.freeze({ ...CASES, DOES_NOT_EXIST }); +export const TEST_CASES: Record = Object.freeze({ + ...CASES, + DOES_NOT_EXIST, +}); const createRequest = ({ type, id, namespace }: BulkUpdateTestCase) => ({ type, diff --git a/x-pack/test/saved_object_api_integration/common/suites/create.ts b/x-pack/test/saved_object_api_integration/common/suites/create.ts index 2a5ab696c4f53..7e28d5ed9ed94 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/create.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/create.ts @@ -13,8 +13,9 @@ import { expectResponses, getUrlPrefix, getTestTitle, + getRedactedNamespaces, } from '../lib/saved_object_test_utils'; -import { ExpectResponseBody, TestCase, TestDefinition, TestSuite } from '../lib/types'; +import { ExpectResponseBody, TestCase, TestDefinition, TestSuite, TestUser } from '../lib/types'; export interface CreateTestDefinition extends TestDefinition { request: { type: string; id: string }; @@ -33,7 +34,7 @@ const NEW_ATTRIBUTE_VAL = `New attribute value ${Date.now()}`; const NEW_SINGLE_NAMESPACE_OBJ = Object.freeze({ type: 'dashboard', id: '' }); const NEW_MULTI_NAMESPACE_OBJ = Object.freeze({ type: 'sharedtype', id: 'new-sharedtype-id' }); const NEW_NAMESPACE_AGNOSTIC_OBJ = Object.freeze({ type: 'globaltype', id: 'new-globaltype-id' }); -export const TEST_CASES = Object.freeze({ +export const TEST_CASES: Record = Object.freeze({ ...CASES, NEW_SINGLE_NAMESPACE_OBJ, NEW_MULTI_NAMESPACE_OBJ, @@ -44,7 +45,7 @@ export function createTestSuiteFactory(es: any, esArchiver: any, supertest: Supe const expectForbidden = expectResponses.forbiddenTypes('create'); const expectResponseBody = ( testCase: CreateTestCase, - spaceId = SPACES.DEFAULT.spaceId + user?: TestUser ): ExpectResponseBody => async (response: Record) => { if (testCase.failure === 403) { await expectForbidden(testCase.type)(response); @@ -54,7 +55,8 @@ export function createTestSuiteFactory(es: any, esArchiver: any, supertest: Supe await expectResponses.permitted(object, testCase); if (!testCase.failure) { expect(object.attributes[NEW_ATTRIBUTE_KEY]).to.eql(NEW_ATTRIBUTE_VAL); - await expectResponses.successCreated(es, spaceId, object.type, object.id); + const redactedNamespaces = getRedactedNamespaces(user, testCase.expectedNamespaces); + expect(object.namespaces).to.eql(redactedNamespaces); } } }; @@ -64,6 +66,7 @@ export function createTestSuiteFactory(es: any, esArchiver: any, supertest: Supe overwrite: boolean, options?: { spaceId?: string; + user?: TestUser; responseBodyOverride?: ExpectResponseBody; } ): CreateTestDefinition[] => { @@ -76,7 +79,7 @@ export function createTestSuiteFactory(es: any, esArchiver: any, supertest: Supe title: getTestTitle(x), responseStatusCode: x.failure ?? 200, request: createRequest(x), - responseBody: options?.responseBodyOverride || expectResponseBody(x, options?.spaceId), + responseBody: options?.responseBodyOverride || expectResponseBody(x, options?.user), overwrite, })); }; diff --git a/x-pack/test/saved_object_api_integration/common/suites/delete.ts b/x-pack/test/saved_object_api_integration/common/suites/delete.ts index 3179b1b0c9ac5..228e7977f99ac 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/delete.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/delete.ts @@ -25,7 +25,10 @@ export interface DeleteTestCase extends TestCase { } const DOES_NOT_EXIST = Object.freeze({ type: 'dashboard', id: 'does-not-exist' }); -export const TEST_CASES = Object.freeze({ ...CASES, DOES_NOT_EXIST }); +export const TEST_CASES: Record = Object.freeze({ + ...CASES, + DOES_NOT_EXIST, +}); export function deleteTestSuiteFactory(esArchiver: any, supertest: SuperTest) { const expectForbidden = expectResponses.forbiddenTypes('delete'); diff --git a/x-pack/test/saved_object_api_integration/common/suites/export.ts b/x-pack/test/saved_object_api_integration/common/suites/export.ts index 4a8eff1fb380c..4eb967a952c60 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/export.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/export.ts @@ -30,7 +30,10 @@ export interface ExportTestCase { type: string; id?: string; successResult?: SuccessResult | SuccessResult[]; - failure?: 400 | 403; + failure?: { + statusCode: 200 | 400 | 403; // if the user searches for only types they are not authorized for, they will get an empty 200 result + reason: 'unauthorized' | 'bad_request'; + }; } // additional sharedtype objects that exist but do not have common test cases defined @@ -90,41 +93,45 @@ export const getTestCases = (spaceId?: string): { [key: string]: ExportTestCase type: 'globaltype', successResult: CASES.NAMESPACE_AGNOSTIC, }, - hiddenObject: { title: 'hidden object', ...CASES.HIDDEN, failure: 400 }, - hiddenType: { title: 'hidden type', type: 'hiddentype', failure: 400 }, + hiddenObject: { + title: 'hidden object', + ...CASES.HIDDEN, + failure: { statusCode: 400, reason: 'bad_request' }, + }, + hiddenType: { + title: 'hidden type', + type: 'hiddentype', + failure: { statusCode: 400, reason: 'bad_request' }, + }, }); export const createRequest = ({ type, id }: ExportTestCase) => id ? { objects: [{ type, id }] } : { type }; -const getTestTitle = ({ failure, title }: ExportTestCase) => { - let description = 'success'; - if (failure === 400) { - description = 'bad request'; - } else if (failure === 403) { - description = 'forbidden'; - } - return `${description} ["${title}"]`; -}; +const getTestTitle = ({ failure, title }: ExportTestCase) => + `${failure?.reason || 'success'} ["${title}"]`; + +const EMPTY_RESULT = { exportedCount: 0, missingRefCount: 0, missingReferences: [] }; export function exportTestSuiteFactory(esArchiver: any, supertest: SuperTest) { const expectForbiddenBulkGet = expectResponses.forbiddenTypes('bulk_get'); - const expectForbiddenFind = expectResponses.forbiddenTypes('find'); const expectResponseBody = (testCase: ExportTestCase): ExpectResponseBody => async ( response: Record ) => { const { type, id, successResult = { type, id } as SuccessResult, failure } = testCase; - if (failure === 403) { - // In export only, the API uses "bulk_get" or "find" depending on the parameters it receives. - // The best that could be done here is to have an if statement to ensure at least one of the - // two errors has been thrown. - if (id) { + if (failure?.reason === 'unauthorized') { + // In export only, the API uses "bulkGet" or "find" depending on the parameters it receives. + if (failure.statusCode === 403) { + // "bulkGet" was unauthorized, which returns a forbidden error await expectForbiddenBulkGet(type)(response); + } else if (failure.statusCode === 200) { + // "find" was unauthorized, which returns an empty result + expect(response.body).not.to.have.property('error'); + expect(response.text).to.equal(JSON.stringify(EMPTY_RESULT)); } else { - await expectForbiddenFind(type)(response); + throw new Error(`Unexpected failure status code: ${failure.statusCode}`); } - } else if (failure === 400) { - // 400 + } else if (failure?.reason === 'bad_request') { expect(response.body.error).to.eql('Bad Request'); - expect(response.body.statusCode).to.eql(failure); + expect(response.body.statusCode).to.eql(failure.statusCode); if (id) { expect(response.body.message).to.eql( `Trying to export object(s) with non-exportable types: ${type}:${id}` @@ -132,6 +139,8 @@ export function exportTestSuiteFactory(esArchiver: any, supertest: SuperTest { let cases = Array.isArray(testCases) ? testCases : [testCases]; - if (forbidden) { + if (failure) { // override the expected result in each test case - cases = cases.map((x) => ({ ...x, failure: 403 })); + cases = cases.map((x) => ({ ...x, failure })); } return cases.map((x) => ({ title: getTestTitle(x), - responseStatusCode: x.failure ?? 200, + responseStatusCode: x.failure?.statusCode ?? 200, request: createRequest(x), responseBody: options?.responseBodyOverride || expectResponseBody(x), })); diff --git a/x-pack/test/saved_object_api_integration/common/suites/find.ts b/x-pack/test/saved_object_api_integration/common/suites/find.ts index bab4a4d88534a..381306f810122 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/find.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/find.ts @@ -7,10 +7,13 @@ import expect from '@kbn/expect'; import { SuperTest } from 'supertest'; import querystring from 'querystring'; -import { Assign } from '@kbn/utility-types'; -import { SAVED_OBJECT_TEST_CASES as CASES } from '../lib/saved_object_test_cases'; +import { SAVED_OBJECT_TEST_CASES, CONFLICT_TEST_CASES } from '../lib/saved_object_test_cases'; import { SPACES } from '../lib/spaces'; -import { expectResponses, getUrlPrefix } from '../lib/saved_object_test_utils'; +import { + getUrlPrefix, + isUserAuthorizedAtSpace, + getRedactedNamespaces, +} from '../lib/saved_object_test_utils'; import { ExpectResponseBody, TestCase, TestDefinition, TestSuite, TestUser } from '../lib/types'; const { @@ -22,80 +25,34 @@ export interface FindTestDefinition extends TestDefinition { } export type FindTestSuite = TestSuite; -type FindSavedObjectCase = Assign; - export interface FindTestCase { title: string; query: string; successResult?: { - savedObjects?: FindSavedObjectCase | FindSavedObjectCase[]; + savedObjects?: TestCase | TestCase[]; page?: number; perPage?: number; total?: number; }; failure?: { - statusCode: 400 | 403; - reason: - | 'forbidden_types' - | 'forbidden_namespaces' - | 'cross_namespace_not_permitted' - | 'bad_request'; + statusCode: 200 | 400; // if the user searches for types and/or namespaces they are not authorized for, they will get a 200 result with those types/namespaces omitted + reason: 'unauthorized' | 'cross_namespace_not_permitted' | 'bad_request'; }; } -// additional sharedtype objects that exist but do not have common test cases defined -const CONFLICT_1_OBJ = Object.freeze({ - type: 'sharedtype', - id: 'conflict_1', - namespaces: ['default', 'space_1', 'space_2'], -}); -const CONFLICT_2A_OBJ = Object.freeze({ - type: 'sharedtype', - id: 'conflict_2a', - originId: 'conflict_2', - namespaces: ['default', 'space_1', 'space_2'], -}); -const CONFLICT_2B_OBJ = Object.freeze({ - type: 'sharedtype', - id: 'conflict_2b', - originId: 'conflict_2', - namespaces: ['default', 'space_1', 'space_2'], -}); -const CONFLICT_3_OBJ = Object.freeze({ - type: 'sharedtype', - id: 'conflict_3', - namespaces: ['default', 'space_1', 'space_2'], -}); -const CONFLICT_4A_OBJ = Object.freeze({ - type: 'sharedtype', - id: 'conflict_4a', - originId: 'conflict_4', - namespaces: ['default', 'space_1', 'space_2'], -}); - const TEST_CASES = [ - { ...CASES.SINGLE_NAMESPACE_DEFAULT_SPACE, namespaces: ['default'] }, - { ...CASES.SINGLE_NAMESPACE_SPACE_1, namespaces: ['space_1'] }, - { ...CASES.SINGLE_NAMESPACE_SPACE_2, namespaces: ['space_2'] }, - { ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, namespaces: ['default', 'space_1'] }, - { ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, namespaces: ['space_1'] }, - { ...CASES.MULTI_NAMESPACE_ONLY_SPACE_2, namespaces: ['space_2'] }, - { ...CASES.NAMESPACE_AGNOSTIC, namespaces: undefined }, - { ...CASES.HIDDEN, namespaces: undefined }, + ...Object.values(SAVED_OBJECT_TEST_CASES), + ...Object.values(CONFLICT_TEST_CASES), ]; -expect(TEST_CASES.length).to.eql( - Object.values(CASES).length, - 'Unhandled test cases in `find` suite' -); - export const getTestCases = ( { currentSpace, crossSpaceSearch }: { currentSpace?: string; crossSpaceSearch?: string[] } = { currentSpace: undefined, crossSpaceSearch: undefined, } ) => { - const crossSpaceIds = crossSpaceSearch?.filter((s) => s !== (currentSpace ?? 'default')) ?? []; + const crossSpaceIds = + crossSpaceSearch?.filter((s) => s !== (currentSpace ?? DEFAULT_SPACE_ID)) ?? []; // intentionally exclude the current space const isCrossSpaceSearch = crossSpaceIds.length > 0; const isWildcardSearch = crossSpaceIds.includes('*'); @@ -104,7 +61,7 @@ export const getTestCases = ( : ''; const buildTitle = (title: string) => - crossSpaceSearch ? `${title} (cross-space ${isWildcardSearch ? 'with wildcard' : ''})` : title; + crossSpaceSearch ? `${title} (cross-space${isWildcardSearch ? ' with wildcard' : ''})` : title; type CasePredicate = (testCase: TestCase) => boolean; const getExpectedSavedObjects = (predicate: CasePredicate) => { @@ -117,13 +74,16 @@ export const getTestCases = ( return TEST_CASES.filter((t) => { const hasOtherNamespaces = - Array.isArray(t.namespaces) && - t.namespaces!.some((ns) => ns !== (currentSpace ?? 'default')); + !t.expectedNamespaces || // namespace-agnostic types do not have an expectedNamespaces field + t.expectedNamespaces.some((ns) => ns !== (currentSpace ?? DEFAULT_SPACE_ID)); return hasOtherNamespaces && predicate(t); }); } return TEST_CASES.filter( - (t) => (!t.namespaces || t.namespaces.includes(currentSpace ?? 'default')) && predicate(t) + (t) => + (!t.expectedNamespaces || + t.expectedNamespaces.includes(currentSpace ?? DEFAULT_SPACE_ID)) && + predicate(t) ); }; @@ -140,19 +100,13 @@ export const getTestCases = ( query: `type=sharedtype&fields=title${namespacesQueryParam}`, successResult: { // expected depends on which spaces the user is authorized against... - savedObjects: getExpectedSavedObjects((t) => t.type === 'sharedtype').concat( - CONFLICT_1_OBJ, - CONFLICT_2A_OBJ, - CONFLICT_2B_OBJ, - CONFLICT_3_OBJ, - CONFLICT_4A_OBJ - ), + savedObjects: getExpectedSavedObjects((t) => t.type === 'sharedtype'), }, } as FindTestCase, namespaceAgnosticType: { title: buildTitle('find namespace-agnostic type'), query: `type=globaltype&fields=title${namespacesQueryParam}`, - successResult: { savedObjects: CASES.NAMESPACE_AGNOSTIC }, + successResult: { savedObjects: SAVED_OBJECT_TEST_CASES.NAMESPACE_AGNOSTIC }, } as FindTestCase, hiddenType: { title: buildTitle('find hidden type'), @@ -162,6 +116,15 @@ export const getTestCases = ( title: buildTitle('find unknown type'), query: `type=wigwags${namespacesQueryParam}`, } as FindTestCase, + eachType: { + title: buildTitle('find each type'), + query: `type=isolatedtype&type=sharedtype&type=globaltype&type=hiddentype&type=wigwags${namespacesQueryParam}`, + successResult: { + savedObjects: getExpectedSavedObjects((t) => + ['isolatedtype', 'sharedtype', 'globaltype'].includes(t.type) + ), + }, + } as FindTestCase, pageBeyondTotal: { title: buildTitle('find page beyond total'), query: `type=isolatedtype&page=100&per_page=100${namespacesQueryParam}`, @@ -179,7 +142,7 @@ export const getTestCases = ( filterWithNamespaceAgnosticType: { title: buildTitle('filter with namespace-agnostic type'), query: `type=globaltype&filter=globaltype.attributes.title:*global*${namespacesQueryParam}`, - successResult: { savedObjects: CASES.NAMESPACE_AGNOSTIC }, + successResult: { savedObjects: SAVED_OBJECT_TEST_CASES.NAMESPACE_AGNOSTIC }, } as FindTestCase, filterWithHiddenType: { title: buildTitle('filter with hidden type'), @@ -200,49 +163,48 @@ export const getTestCases = ( }; }; +function objectComparator(a: { id: string }, b: { id: string }) { + return a.id > b.id ? 1 : a.id < b.id ? -1 : 0; +} + export const createRequest = ({ query }: FindTestCase) => ({ query }); -const getTestTitle = ({ failure, title }: FindTestCase) => { - let description = 'success'; - if (failure?.statusCode === 400) { - description = 'bad request'; - } else if (failure?.statusCode === 403) { - description = 'forbidden'; - } - return `${description} ["${title}"]`; -}; +const getTestTitle = ({ failure, title }: FindTestCase) => + `${failure?.reason || 'success'} ["${title}"]`; export function findTestSuiteFactory(esArchiver: any, supertest: SuperTest) { - const expectForbiddenTypes = expectResponses.forbiddenTypes('find'); - const expectForbiddeNamespaces = expectResponses.forbiddenSpaces; const expectResponseBody = ( testCase: FindTestCase, user?: TestUser ): ExpectResponseBody => async (response: Record) => { const { failure, successResult = {}, query } = testCase; const parsedQuery = querystring.parse(query); - if (failure?.statusCode === 403) { - if (failure?.reason === 'forbidden_types') { - const type = parsedQuery.type; - await expectForbiddenTypes(type)(response); - } else if (failure?.reason === 'forbidden_namespaces') { - await expectForbiddeNamespaces(response); + if (failure?.statusCode === 200) { + if (failure?.reason === 'unauthorized') { + // if the user is completely unauthorized, they will receive an empty response body + const expected = { + page: parsedQuery.page || 1, + per_page: parsedQuery.per_page || 20, + total: 0, + saved_objects: [], + }; + expect(response.body).to.eql(expected); } else { - throw new Error(`Unexpected failure reason: ${failure?.reason}`); + throw new Error(`Unexpected failure reason: ${failure.reason}`); } } else if (failure?.statusCode === 400) { - if (failure?.reason === 'bad_request') { + if (failure.reason === 'bad_request') { const type = (parsedQuery.filter as string).split('.')[0]; expect(response.body.error).to.eql('Bad Request'); - expect(response.body.statusCode).to.eql(failure?.statusCode); + expect(response.body.statusCode).to.eql(failure.statusCode); expect(response.body.message).to.eql(`This type ${type} is not allowed: Bad Request`); - } else if (failure?.reason === 'cross_namespace_not_permitted') { + } else if (failure.reason === 'cross_namespace_not_permitted') { expect(response.body.error).to.eql('Bad Request'); - expect(response.body.statusCode).to.eql(failure?.statusCode); + expect(response.body.statusCode).to.eql(failure.statusCode); expect(response.body.message).to.eql( `_find across namespaces is not permitted when the Spaces plugin is disabled.: Bad Request` ); } else { - throw new Error(`Unexpected failure reason: ${failure?.reason}`); + throw new Error(`Unexpected failure reason: ${failure.reason}`); } } else { // 2xx @@ -251,11 +213,8 @@ export function findTestSuiteFactory(esArchiver: any, supertest: SuperTest) const savedObjectsArray = Array.isArray(savedObjects) ? savedObjects : [savedObjects]; const authorizedSavedObjects = savedObjectsArray.filter( (so) => - !user || - !so.namespaces || - so.namespaces.some( - (ns) => user.authorizedAtSpaces.includes(ns) || user.authorizedAtSpaces.includes('*') - ) + !so.expectedNamespaces || + so.expectedNamespaces.some((x) => isUserAuthorizedAtSpace(user, x)) ); expect(response.body.page).to.eql(page); expect(response.body.per_page).to.eql(perPage); @@ -265,16 +224,17 @@ export function findTestSuiteFactory(esArchiver: any, supertest: SuperTest) expect(response.body.total).to.eql(total || authorizedSavedObjects.length); } - authorizedSavedObjects.sort((s1, s2) => (s1.id < s2.id ? -1 : 1)); - response.body.saved_objects.sort((s1: any, s2: any) => (s1.id < s2.id ? -1 : 1)); + authorizedSavedObjects.sort(objectComparator); + response.body.saved_objects.sort(objectComparator); for (let i = 0; i < authorizedSavedObjects.length; i++) { const object = response.body.saved_objects[i]; - const { type: expectedType, id: expectedId } = authorizedSavedObjects[i]; - expect(object.type).to.eql(expectedType); - expect(object.id).to.eql(expectedId); + const expected = authorizedSavedObjects[i]; + const expectedNamespaces = getRedactedNamespaces(user, expected.expectedNamespaces); + expect(object.type).to.eql(expected.type); + expect(object.id).to.eql(expected.id); expect(object.updated_at).to.match(/^[\d-]{10}T[\d:\.]{12}Z$/); - expect(object.namespaces).to.eql(object.namespaces); + expect(object.namespaces).to.eql(expectedNamespaces); // don't test attributes, version, or references } } diff --git a/x-pack/test/saved_object_api_integration/common/suites/get.ts b/x-pack/test/saved_object_api_integration/common/suites/get.ts index fb03cd548d41a..8d8938b5ee79f 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/get.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/get.ts @@ -21,7 +21,7 @@ export type GetTestSuite = TestSuite; export type GetTestCase = TestCase; const DOES_NOT_EXIST = Object.freeze({ type: 'dashboard', id: 'does-not-exist' }); -export const TEST_CASES = Object.freeze({ ...CASES, DOES_NOT_EXIST }); +export const TEST_CASES: Record = Object.freeze({ ...CASES, DOES_NOT_EXIST }); export function getTestSuiteFactory(esArchiver: any, supertest: SuperTest) { const expectForbidden = expectResponses.forbiddenTypes('get'); diff --git a/x-pack/test/saved_object_api_integration/common/suites/import.ts b/x-pack/test/saved_object_api_integration/common/suites/import.ts index 5036d7b200881..b0d0b4f8a815a 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/import.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/import.ts @@ -36,7 +36,7 @@ const NEW_ATTRIBUTE_VAL = `New attribute value ${Date.now()}`; // * id: conflict_4a, originId: conflict_4 // using the seven conflict test case objects below, we can exercise various permutations of exact/inexact/ambiguous conflict scenarios const CID = 'conflict_'; -export const TEST_CASES = Object.freeze({ +export const TEST_CASES: Record = Object.freeze({ ...CASES, CONFLICT_1_OBJ: Object.freeze({ type: 'sharedtype', id: `${CID}1` }), CONFLICT_1A_OBJ: Object.freeze({ type: 'sharedtype', id: `${CID}1a`, originId: `${CID}1` }), diff --git a/x-pack/test/saved_object_api_integration/common/suites/resolve_import_errors.ts b/x-pack/test/saved_object_api_integration/common/suites/resolve_import_errors.ts index 6d294aed9b4de..02fa614ac2b55 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/resolve_import_errors.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/resolve_import_errors.ts @@ -37,7 +37,7 @@ const NEW_ATTRIBUTE_VAL = `New attribute value ${Date.now()}`; // * id: conflict_3 // * id: conflict_4a, originId: conflict_4 // using the five conflict test case objects below, we can exercise various permutations of exact/inexact/ambiguous conflict scenarios -export const TEST_CASES = Object.freeze({ +export const TEST_CASES: Record = Object.freeze({ ...CASES, CONFLICT_1A_OBJ: Object.freeze({ type: 'sharedtype', diff --git a/x-pack/test/saved_object_api_integration/common/suites/update.ts b/x-pack/test/saved_object_api_integration/common/suites/update.ts index 82f4699babf46..19921a82b2eb4 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/update.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/update.ts @@ -28,7 +28,10 @@ const NEW_ATTRIBUTE_KEY = 'title'; // all type mappings include this attribute, const NEW_ATTRIBUTE_VAL = `Updated attribute value ${Date.now()}`; const DOES_NOT_EXIST = Object.freeze({ type: 'dashboard', id: 'does-not-exist' }); -export const TEST_CASES = Object.freeze({ ...CASES, DOES_NOT_EXIST }); +export const TEST_CASES: Record = Object.freeze({ + ...CASES, + DOES_NOT_EXIST, +}); export function updateTestSuiteFactory(esArchiver: any, supertest: SuperTest) { const expectForbidden = expectResponses.forbiddenTypes('update'); diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_create.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_create.ts index 0cc5969e2b7ab..93ae439d01166 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_create.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_create.ts @@ -26,13 +26,23 @@ const unresolvableConflict = (condition?: boolean) => const createTestCases = (overwrite: boolean, spaceId: string) => { // for each permitted (non-403) outcome, if failure !== undefined then we expect // to receive an error; otherwise, we expect to receive a success result + const expectedNamespaces = [spaceId]; // newly created objects should have this `namespaces` array in their return value const normalTypes = [ { ...CASES.SINGLE_NAMESPACE_DEFAULT_SPACE, ...fail409(!overwrite && spaceId === DEFAULT_SPACE_ID), + expectedNamespaces, + }, + { + ...CASES.SINGLE_NAMESPACE_SPACE_1, + ...fail409(!overwrite && spaceId === SPACE_1_ID), + expectedNamespaces, + }, + { + ...CASES.SINGLE_NAMESPACE_SPACE_2, + ...fail409(!overwrite && spaceId === SPACE_2_ID), + expectedNamespaces, }, - { ...CASES.SINGLE_NAMESPACE_SPACE_1, ...fail409(!overwrite && spaceId === SPACE_1_ID) }, - { ...CASES.SINGLE_NAMESPACE_SPACE_2, ...fail409(!overwrite && spaceId === SPACE_2_ID) }, { ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, ...fail409(!overwrite || (spaceId !== DEFAULT_SPACE_ID && spaceId !== SPACE_1_ID)), @@ -49,8 +59,8 @@ const createTestCases = (overwrite: boolean, spaceId: string) => { ...unresolvableConflict(spaceId !== SPACE_2_ID), }, { ...CASES.NAMESPACE_AGNOSTIC, ...fail409(!overwrite) }, - CASES.NEW_SINGLE_NAMESPACE_OBJ, - CASES.NEW_MULTI_NAMESPACE_OBJ, + { ...CASES.NEW_SINGLE_NAMESPACE_OBJ, expectedNamespaces }, + { ...CASES.NEW_MULTI_NAMESPACE_OBJ, expectedNamespaces }, CASES.NEW_NAMESPACE_AGNOSTIC_OBJ, ]; const hiddenType = [{ ...CASES.HIDDEN, ...fail400() }]; @@ -68,22 +78,28 @@ export default function ({ getService }: FtrProviderContext) { esArchiver, supertest ); - const createTests = (overwrite: boolean, spaceId: string) => { + const createTests = (overwrite: boolean, spaceId: string, user: TestUser) => { const { normalTypes, hiddenType, allTypes } = createTestCases(overwrite, spaceId); // use singleRequest to reduce execution time and/or test combined cases return { - unauthorized: createTestDefinitions(allTypes, true, overwrite, { spaceId }), + unauthorized: createTestDefinitions(allTypes, true, overwrite, { spaceId, user }), authorized: [ - createTestDefinitions(normalTypes, false, overwrite, { spaceId, singleRequest: true }), - createTestDefinitions(hiddenType, true, overwrite, { spaceId }), + createTestDefinitions(normalTypes, false, overwrite, { + spaceId, + user, + singleRequest: true, + }), + createTestDefinitions(hiddenType, true, overwrite, { spaceId, user }), createTestDefinitions(allTypes, true, overwrite, { spaceId, + user, singleRequest: true, responseBodyOverride: expectForbidden(['hiddentype']), }), ].flat(), superuser: createTestDefinitions(allTypes, false, overwrite, { spaceId, + user, singleRequest: true, }), }; @@ -93,7 +109,6 @@ export default function ({ getService }: FtrProviderContext) { getTestScenarios([false, true]).securityAndSpaces.forEach( ({ spaceId, users, modifier: overwrite }) => { const suffix = ` within the ${spaceId} space${overwrite ? ' with overwrite enabled' : ''}`; - const { unauthorized, authorized, superuser } = createTests(overwrite!, spaceId); const _addTests = (user: TestUser, tests: BulkCreateTestDefinition[]) => { addTests(`${user.description}${suffix}`, { user, spaceId, tests }); }; @@ -106,11 +121,14 @@ export default function ({ getService }: FtrProviderContext) { users.readAtSpace, users.allAtOtherSpace, ].forEach((user) => { + const { unauthorized } = createTests(overwrite!, spaceId, user); _addTests(user, unauthorized); }); [users.dualAll, users.allGlobally, users.allAtSpace].forEach((user) => { + const { authorized } = createTests(overwrite!, spaceId, user); _addTests(user, authorized); }); + const { superuser } = createTests(overwrite!, spaceId, users.superuser); _addTests(users.superuser, superuser); } ); diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/create.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/create.ts index f81488603dc83..7353dafb5e1b5 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/create.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/create.ts @@ -24,13 +24,23 @@ const { fail400, fail409 } = testCaseFailures; const createTestCases = (overwrite: boolean, spaceId: string) => { // for each permitted (non-403) outcome, if failure !== undefined then we expect // to receive an error; otherwise, we expect to receive a success result + const expectedNamespaces = [spaceId]; // newly created objects should have this `namespaces` array in their return value const normalTypes = [ { ...CASES.SINGLE_NAMESPACE_DEFAULT_SPACE, ...fail409(!overwrite && spaceId === DEFAULT_SPACE_ID), + expectedNamespaces, + }, + { + ...CASES.SINGLE_NAMESPACE_SPACE_1, + ...fail409(!overwrite && spaceId === SPACE_1_ID), + expectedNamespaces, + }, + { + ...CASES.SINGLE_NAMESPACE_SPACE_2, + ...fail409(!overwrite && spaceId === SPACE_2_ID), + expectedNamespaces, }, - { ...CASES.SINGLE_NAMESPACE_SPACE_1, ...fail409(!overwrite && spaceId === SPACE_1_ID) }, - { ...CASES.SINGLE_NAMESPACE_SPACE_2, ...fail409(!overwrite && spaceId === SPACE_2_ID) }, { ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, ...fail409(!overwrite || (spaceId !== DEFAULT_SPACE_ID && spaceId !== SPACE_1_ID)), @@ -38,8 +48,8 @@ const createTestCases = (overwrite: boolean, spaceId: string) => { { ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, ...fail409(!overwrite || spaceId !== SPACE_1_ID) }, { ...CASES.MULTI_NAMESPACE_ONLY_SPACE_2, ...fail409(!overwrite || spaceId !== SPACE_2_ID) }, { ...CASES.NAMESPACE_AGNOSTIC, ...fail409(!overwrite) }, - CASES.NEW_SINGLE_NAMESPACE_OBJ, - CASES.NEW_MULTI_NAMESPACE_OBJ, + { ...CASES.NEW_SINGLE_NAMESPACE_OBJ, expectedNamespaces }, + { ...CASES.NEW_MULTI_NAMESPACE_OBJ, expectedNamespaces }, CASES.NEW_NAMESPACE_AGNOSTIC_OBJ, ]; const hiddenType = [{ ...CASES.HIDDEN, ...fail400() }]; @@ -53,15 +63,15 @@ export default function ({ getService }: FtrProviderContext) { const es = getService('legacyEs'); const { addTests, createTestDefinitions } = createTestSuiteFactory(es, esArchiver, supertest); - const createTests = (overwrite: boolean, spaceId: string) => { + const createTests = (overwrite: boolean, spaceId: string, user: TestUser) => { const { normalTypes, hiddenType, allTypes } = createTestCases(overwrite, spaceId); return { - unauthorized: createTestDefinitions(allTypes, true, overwrite, { spaceId }), + unauthorized: createTestDefinitions(allTypes, true, overwrite, { spaceId, user }), authorized: [ - createTestDefinitions(normalTypes, false, overwrite, { spaceId }), - createTestDefinitions(hiddenType, true, overwrite, { spaceId }), + createTestDefinitions(normalTypes, false, overwrite, { spaceId, user }), + createTestDefinitions(hiddenType, true, overwrite, { spaceId, user }), ].flat(), - superuser: createTestDefinitions(allTypes, false, overwrite, { spaceId }), + superuser: createTestDefinitions(allTypes, false, overwrite, { spaceId, user }), }; }; @@ -69,7 +79,6 @@ export default function ({ getService }: FtrProviderContext) { getTestScenarios([false, true]).securityAndSpaces.forEach( ({ spaceId, users, modifier: overwrite }) => { const suffix = ` within the ${spaceId} space${overwrite ? ' with overwrite enabled' : ''}`; - const { unauthorized, authorized, superuser } = createTests(overwrite!, spaceId); const _addTests = (user: TestUser, tests: CreateTestDefinition[]) => { addTests(`${user.description}${suffix}`, { user, spaceId, tests }); }; @@ -82,11 +91,14 @@ export default function ({ getService }: FtrProviderContext) { users.readAtSpace, users.allAtOtherSpace, ].forEach((user) => { + const { unauthorized } = createTests(overwrite!, spaceId, user); _addTests(user, unauthorized); }); [users.dualAll, users.allGlobally, users.allAtSpace].forEach((user) => { + const { authorized } = createTests(overwrite!, spaceId, user); _addTests(user, authorized); }); + const { superuser } = createTests(overwrite!, spaceId, users.superuser); _addTests(users.superuser, superuser); } ); diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/export.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/export.ts index c581a1757565e..be3906209032f 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/export.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/export.ts @@ -15,17 +15,23 @@ import { const createTestCases = (spaceId: string) => { const cases = getTestCases(spaceId); - const exportableTypes = [ + const exportableObjects = [ cases.singleNamespaceObject, - cases.singleNamespaceType, cases.multiNamespaceObject, - cases.multiNamespaceType, cases.namespaceAgnosticObject, + ]; + const exportableTypes = [ + cases.singleNamespaceType, + cases.multiNamespaceType, cases.namespaceAgnosticType, ]; - const nonExportableTypes = [cases.hiddenObject, cases.hiddenType]; - const allTypes = exportableTypes.concat(nonExportableTypes); - return { exportableTypes, nonExportableTypes, allTypes }; + const nonExportableObjectsAndTypes = [cases.hiddenObject, cases.hiddenType]; + const allObjectsAndTypes = [ + exportableObjects, + exportableTypes, + nonExportableObjectsAndTypes, + ].flat(); + return { exportableObjects, exportableTypes, nonExportableObjectsAndTypes, allObjectsAndTypes }; }; export default function ({ getService }: FtrProviderContext) { @@ -34,13 +40,19 @@ export default function ({ getService }: FtrProviderContext) { const { addTests, createTestDefinitions } = exportTestSuiteFactory(esArchiver, supertest); const createTests = (spaceId: string) => { - const { exportableTypes, nonExportableTypes, allTypes } = createTestCases(spaceId); + const { + exportableObjects, + exportableTypes, + nonExportableObjectsAndTypes, + allObjectsAndTypes, + } = createTestCases(spaceId); return { unauthorized: [ - createTestDefinitions(exportableTypes, true), - createTestDefinitions(nonExportableTypes, false), + createTestDefinitions(exportableObjects, { statusCode: 403, reason: 'unauthorized' }), + createTestDefinitions(exportableTypes, { statusCode: 200, reason: 'unauthorized' }), // failure with empty result + createTestDefinitions(nonExportableObjectsAndTypes, false), ].flat(), - authorized: createTestDefinitions(allTypes, false), + authorized: createTestDefinitions(allObjectsAndTypes, false), }; }; diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/find.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/find.ts index 6ac77507df473..afd4783fab792 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/find.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/find.ts @@ -4,18 +4,30 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getTestScenarios } from '../../common/lib/saved_object_test_utils'; +import { SPACES } from '../../common/lib/spaces'; +import { AUTHENTICATION } from '../../common/lib/authentication'; +import { + getTestScenarios, + isUserAuthorizedAtSpace, +} from '../../common/lib/saved_object_test_utils'; import { TestUser } from '../../common/lib/types'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { findTestSuiteFactory, getTestCases } from '../../common/suites/find'; -const createTestCases = (currentSpace: string, crossSpaceSearch: string[]) => { +const { + DEFAULT: { spaceId: DEFAULT_SPACE_ID }, + SPACE_1: { spaceId: SPACE_1_ID }, + SPACE_2: { spaceId: SPACE_2_ID }, +} = SPACES; + +const createTestCases = (currentSpace: string, crossSpaceSearch?: string[]) => { const cases = getTestCases({ currentSpace, crossSpaceSearch }); const normalTypes = [ cases.singleNamespaceType, cases.multiNamespaceType, cases.namespaceAgnosticType, + cases.eachType, cases.pageBeyondTotal, cases.unknownSearchField, cases.filterWithNamespaceAgnosticType, @@ -37,89 +49,72 @@ export default function ({ getService }: FtrProviderContext) { const { addTests, createTestDefinitions } = findTestSuiteFactory(esArchiver, supertest); const createTests = (spaceId: string, user: TestUser) => { - const currentSpaceCases = createTestCases(spaceId, []); + const currentSpaceCases = createTestCases(spaceId); - const explicitCrossSpace = createTestCases(spaceId, ['default', 'space_1', 'space_2']); + const EACH_SPACE = [DEFAULT_SPACE_ID, SPACE_1_ID, SPACE_2_ID]; + const explicitCrossSpace = createTestCases(spaceId, EACH_SPACE); const wildcardCrossSpace = createTestCases(spaceId, ['*']); - if (user.username === 'elastic') { + if (user.username === AUTHENTICATION.SUPERUSER.username) { return { currentSpace: createTestDefinitions(currentSpaceCases.allTypes, false, { user }), - crossSpace: createTestDefinitions(explicitCrossSpace.allTypes, false, { user }), + crossSpace: [ + createTestDefinitions(explicitCrossSpace.allTypes, false, { user }), + createTestDefinitions(wildcardCrossSpace.allTypes, false, { user }), + ].flat(), }; } - const authorizedAtCurrentSpace = - user.authorizedAtSpaces.includes(spaceId) || user.authorizedAtSpaces.includes('*'); - - const authorizedExplicitCrossSpaces = ['default', 'space_1', 'space_2'].filter( - (s) => - user.authorizedAtSpaces.includes('*') || - (s !== spaceId && user.authorizedAtSpaces.includes(s)) + const isAuthorizedExplicitCrossSpaces = EACH_SPACE.some( + (s) => s !== spaceId && isUserAuthorizedAtSpace(user, s) ); - - const authorizedWildcardCrossSpaces = ['default', 'space_1', 'space_2'].filter( - (s) => user.authorizedAtSpaces.includes('*') || user.authorizedAtSpaces.includes(s) + const isAuthorizedWildcardCrossSpaces = EACH_SPACE.some((s) => + isUserAuthorizedAtSpace(user, s) ); - const explicitCrossSpaceDefinitions = - authorizedExplicitCrossSpaces.length > 0 - ? [ - createTestDefinitions(explicitCrossSpace.normalTypes, false, { user }), - createTestDefinitions( - explicitCrossSpace.hiddenAndUnknownTypes, - { - statusCode: 403, - reason: 'forbidden_types', - }, - { user } - ), - ].flat() - : createTestDefinitions( - explicitCrossSpace.allTypes, - { - statusCode: 403, - reason: 'forbidden_namespaces', - }, + const explicitCrossSpaceDefinitions = isAuthorizedExplicitCrossSpaces + ? [ + createTestDefinitions(explicitCrossSpace.normalTypes, false, { user }), + createTestDefinitions( + explicitCrossSpace.hiddenAndUnknownTypes, + { statusCode: 200, reason: 'unauthorized' }, { user } - ); - - const wildcardCrossSpaceDefinitions = - authorizedWildcardCrossSpaces.length > 0 - ? [ - createTestDefinitions(wildcardCrossSpace.normalTypes, false, { user }), - createTestDefinitions( - wildcardCrossSpace.hiddenAndUnknownTypes, - { - statusCode: 403, - reason: 'forbidden_types', - }, - { user } - ), - ].flat() - : createTestDefinitions( - wildcardCrossSpace.allTypes, - { - statusCode: 403, - reason: 'forbidden_namespaces', - }, + ), + ].flat() + : createTestDefinitions( + explicitCrossSpace.allTypes, + { statusCode: 200, reason: 'unauthorized' }, + { user } + ); + const wildcardCrossSpaceDefinitions = isAuthorizedWildcardCrossSpaces + ? [ + createTestDefinitions(wildcardCrossSpace.normalTypes, false, { user }), + createTestDefinitions( + wildcardCrossSpace.hiddenAndUnknownTypes, + { statusCode: 200, reason: 'unauthorized' }, { user } - ); + ), + ].flat() + : createTestDefinitions( + wildcardCrossSpace.allTypes, + { statusCode: 200, reason: 'unauthorized' }, + { user } + ); return { - currentSpace: authorizedAtCurrentSpace + currentSpace: isUserAuthorizedAtSpace(user, spaceId) ? [ createTestDefinitions(currentSpaceCases.normalTypes, false, { user, }), createTestDefinitions(currentSpaceCases.hiddenAndUnknownTypes, { - statusCode: 403, - reason: 'forbidden_types', + statusCode: 200, + reason: 'unauthorized', }), ].flat() : createTestDefinitions(currentSpaceCases.allTypes, { - statusCode: 403, - reason: 'forbidden_types', + statusCode: 200, + reason: 'unauthorized', }), crossSpace: [...explicitCrossSpaceDefinitions, ...wildcardCrossSpaceDefinitions], }; diff --git a/x-pack/test/saved_object_api_integration/security_only/apis/bulk_create.ts b/x-pack/test/saved_object_api_integration/security_only/apis/bulk_create.ts index 725120687c231..cc2c5e2e7fc00 100644 --- a/x-pack/test/saved_object_api_integration/security_only/apis/bulk_create.ts +++ b/x-pack/test/saved_object_api_integration/security_only/apis/bulk_create.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { SPACES } from '../../common/lib/spaces'; import { testCaseFailures, getTestScenarios } from '../../common/lib/saved_object_test_utils'; import { TestUser } from '../../common/lib/types'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -13,22 +14,26 @@ import { BulkCreateTestDefinition, } from '../../common/suites/bulk_create'; +const { + DEFAULT: { spaceId: DEFAULT_SPACE_ID }, +} = SPACES; const { fail400, fail409 } = testCaseFailures; const unresolvableConflict = () => ({ fail409Param: 'unresolvableConflict' }); const createTestCases = (overwrite: boolean) => { // for each permitted (non-403) outcome, if failure !== undefined then we expect // to receive an error; otherwise, we expect to receive a success result + const expectedNamespaces = [DEFAULT_SPACE_ID]; // newly created objects should have this `namespaces` array in their return value const normalTypes = [ { ...CASES.SINGLE_NAMESPACE_DEFAULT_SPACE, ...fail409(!overwrite) }, - CASES.SINGLE_NAMESPACE_SPACE_1, - CASES.SINGLE_NAMESPACE_SPACE_2, + { ...CASES.SINGLE_NAMESPACE_SPACE_1, expectedNamespaces }, + { ...CASES.SINGLE_NAMESPACE_SPACE_2, expectedNamespaces }, { ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, ...fail409(!overwrite) }, { ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, ...fail409(), ...unresolvableConflict() }, { ...CASES.MULTI_NAMESPACE_ONLY_SPACE_2, ...fail409(), ...unresolvableConflict() }, { ...CASES.NAMESPACE_AGNOSTIC, ...fail409(!overwrite) }, - CASES.NEW_SINGLE_NAMESPACE_OBJ, - CASES.NEW_MULTI_NAMESPACE_OBJ, + { ...CASES.NEW_SINGLE_NAMESPACE_OBJ, expectedNamespaces }, + { ...CASES.NEW_MULTI_NAMESPACE_OBJ, expectedNamespaces }, CASES.NEW_NAMESPACE_AGNOSTIC_OBJ, ]; const hiddenType = [{ ...CASES.HIDDEN, ...fail400() }]; @@ -46,27 +51,27 @@ export default function ({ getService }: FtrProviderContext) { esArchiver, supertest ); - const createTests = (overwrite: boolean) => { + const createTests = (overwrite: boolean, user: TestUser) => { const { normalTypes, hiddenType, allTypes } = createTestCases(overwrite); // use singleRequest to reduce execution time and/or test combined cases return { - unauthorized: createTestDefinitions(allTypes, true, overwrite), + unauthorized: createTestDefinitions(allTypes, true, overwrite, { user }), authorized: [ - createTestDefinitions(normalTypes, false, overwrite, { singleRequest: true }), - createTestDefinitions(hiddenType, true, overwrite), + createTestDefinitions(normalTypes, false, overwrite, { user, singleRequest: true }), + createTestDefinitions(hiddenType, true, overwrite, { user }), createTestDefinitions(allTypes, true, overwrite, { + user, singleRequest: true, responseBodyOverride: expectForbidden(['hiddentype']), }), ].flat(), - superuser: createTestDefinitions(allTypes, false, overwrite, { singleRequest: true }), + superuser: createTestDefinitions(allTypes, false, overwrite, { user, singleRequest: true }), }; }; describe('_bulk_create', () => { getTestScenarios([false, true]).security.forEach(({ users, modifier: overwrite }) => { const suffix = overwrite ? ' with overwrite enabled' : ''; - const { unauthorized, authorized, superuser } = createTests(overwrite!); const _addTests = (user: TestUser, tests: BulkCreateTestDefinition[]) => { addTests(`${user.description}${suffix}`, { user, tests }); }; @@ -81,11 +86,14 @@ export default function ({ getService }: FtrProviderContext) { users.allAtSpace1, users.readAtSpace1, ].forEach((user) => { + const { unauthorized } = createTests(overwrite!, user); _addTests(user, unauthorized); }); [users.dualAll, users.allGlobally].forEach((user) => { + const { authorized } = createTests(overwrite!, user); _addTests(user, authorized); }); + const { superuser } = createTests(overwrite!, users.superuser); _addTests(users.superuser, superuser); }); }); diff --git a/x-pack/test/saved_object_api_integration/security_only/apis/create.ts b/x-pack/test/saved_object_api_integration/security_only/apis/create.ts index 88d096f05d846..b7c6ecef979bd 100644 --- a/x-pack/test/saved_object_api_integration/security_only/apis/create.ts +++ b/x-pack/test/saved_object_api_integration/security_only/apis/create.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { SPACES } from '../../common/lib/spaces'; import { testCaseFailures, getTestScenarios } from '../../common/lib/saved_object_test_utils'; import { TestUser } from '../../common/lib/types'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -13,21 +14,25 @@ import { CreateTestDefinition, } from '../../common/suites/create'; +const { + DEFAULT: { spaceId: DEFAULT_SPACE_ID }, +} = SPACES; const { fail400, fail409 } = testCaseFailures; const createTestCases = (overwrite: boolean) => { // for each permitted (non-403) outcome, if failure !== undefined then we expect // to receive an error; otherwise, we expect to receive a success result + const expectedNamespaces = [DEFAULT_SPACE_ID]; // newly created objects should have this `namespaces` array in their return value const normalTypes = [ { ...CASES.SINGLE_NAMESPACE_DEFAULT_SPACE, ...fail409(!overwrite) }, - CASES.SINGLE_NAMESPACE_SPACE_1, - CASES.SINGLE_NAMESPACE_SPACE_2, + { ...CASES.SINGLE_NAMESPACE_SPACE_1, expectedNamespaces }, + { ...CASES.SINGLE_NAMESPACE_SPACE_2, expectedNamespaces }, { ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, ...fail409(!overwrite) }, { ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, ...fail409() }, { ...CASES.MULTI_NAMESPACE_ONLY_SPACE_2, ...fail409() }, { ...CASES.NAMESPACE_AGNOSTIC, ...fail409(!overwrite) }, - CASES.NEW_SINGLE_NAMESPACE_OBJ, - CASES.NEW_MULTI_NAMESPACE_OBJ, + { ...CASES.NEW_SINGLE_NAMESPACE_OBJ, expectedNamespaces }, + { ...CASES.NEW_MULTI_NAMESPACE_OBJ, expectedNamespaces }, CASES.NEW_NAMESPACE_AGNOSTIC_OBJ, ]; const hiddenType = [{ ...CASES.HIDDEN, ...fail400() }]; @@ -41,22 +46,21 @@ export default function ({ getService }: FtrProviderContext) { const es = getService('legacyEs'); const { addTests, createTestDefinitions } = createTestSuiteFactory(es, esArchiver, supertest); - const createTests = (overwrite: boolean) => { + const createTests = (overwrite: boolean, user: TestUser) => { const { normalTypes, hiddenType, allTypes } = createTestCases(overwrite); return { - unauthorized: createTestDefinitions(allTypes, true, overwrite), + unauthorized: createTestDefinitions(allTypes, true, overwrite, { user }), authorized: [ - createTestDefinitions(normalTypes, false, overwrite), - createTestDefinitions(hiddenType, true, overwrite), + createTestDefinitions(normalTypes, false, overwrite, { user }), + createTestDefinitions(hiddenType, true, overwrite, { user }), ].flat(), - superuser: createTestDefinitions(allTypes, false, overwrite), + superuser: createTestDefinitions(allTypes, false, overwrite, { user }), }; }; describe('_create', () => { getTestScenarios([false, true]).security.forEach(({ users, modifier: overwrite }) => { const suffix = overwrite ? ' with overwrite enabled' : ''; - const { unauthorized, authorized, superuser } = createTests(overwrite!); const _addTests = (user: TestUser, tests: CreateTestDefinition[]) => { addTests(`${user.description}${suffix}`, { user, tests }); }; @@ -71,11 +75,14 @@ export default function ({ getService }: FtrProviderContext) { users.allAtSpace1, users.readAtSpace1, ].forEach((user) => { + const { unauthorized } = createTests(overwrite!, user); _addTests(user, unauthorized); }); [users.dualAll, users.allGlobally].forEach((user) => { + const { authorized } = createTests(overwrite!, user); _addTests(user, authorized); }); + const { superuser } = createTests(overwrite!, users.superuser); _addTests(users.superuser, superuser); }); }); diff --git a/x-pack/test/saved_object_api_integration/security_only/apis/export.ts b/x-pack/test/saved_object_api_integration/security_only/apis/export.ts index 99babf683ccfa..ea1ed56921d22 100644 --- a/x-pack/test/saved_object_api_integration/security_only/apis/export.ts +++ b/x-pack/test/saved_object_api_integration/security_only/apis/export.ts @@ -15,17 +15,23 @@ import { const createTestCases = () => { const cases = getTestCases(); - const exportableTypes = [ + const exportableObjects = [ cases.singleNamespaceObject, - cases.singleNamespaceType, cases.multiNamespaceObject, - cases.multiNamespaceType, cases.namespaceAgnosticObject, + ]; + const exportableTypes = [ + cases.singleNamespaceType, + cases.multiNamespaceType, cases.namespaceAgnosticType, ]; - const nonExportableTypes = [cases.hiddenObject, cases.hiddenType]; - const allTypes = exportableTypes.concat(nonExportableTypes); - return { exportableTypes, nonExportableTypes, allTypes }; + const nonExportableObjectsAndTypes = [cases.hiddenObject, cases.hiddenType]; + const allObjectsAndTypes = [ + exportableObjects, + exportableTypes, + nonExportableObjectsAndTypes, + ].flat(); + return { exportableObjects, exportableTypes, nonExportableObjectsAndTypes, allObjectsAndTypes }; }; export default function ({ getService }: FtrProviderContext) { @@ -34,13 +40,19 @@ export default function ({ getService }: FtrProviderContext) { const { addTests, createTestDefinitions } = exportTestSuiteFactory(esArchiver, supertest); const createTests = () => { - const { exportableTypes, nonExportableTypes, allTypes } = createTestCases(); + const { + exportableObjects, + exportableTypes, + nonExportableObjectsAndTypes, + allObjectsAndTypes, + } = createTestCases(); return { unauthorized: [ - createTestDefinitions(exportableTypes, true), - createTestDefinitions(nonExportableTypes, false), + createTestDefinitions(exportableObjects, { statusCode: 403, reason: 'unauthorized' }), + createTestDefinitions(exportableTypes, { statusCode: 200, reason: 'unauthorized' }), // failure with empty result + createTestDefinitions(nonExportableObjectsAndTypes, false), ].flat(), - authorized: createTestDefinitions(allTypes, false), + authorized: createTestDefinitions(allObjectsAndTypes, false), }; }; diff --git a/x-pack/test/saved_object_api_integration/security_only/apis/find.ts b/x-pack/test/saved_object_api_integration/security_only/apis/find.ts index 3a435119436ca..aa18f32600949 100644 --- a/x-pack/test/saved_object_api_integration/security_only/apis/find.ts +++ b/x-pack/test/saved_object_api_integration/security_only/apis/find.ts @@ -4,18 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ +import { SPACES } from '../../common/lib/spaces'; +import { AUTHENTICATION } from '../../common/lib/authentication'; import { getTestScenarios } from '../../common/lib/saved_object_test_utils'; import { TestUser } from '../../common/lib/types'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { findTestSuiteFactory, getTestCases } from '../../common/suites/find'; -const createTestCases = (crossSpaceSearch: string[]) => { +const { + DEFAULT: { spaceId: DEFAULT_SPACE_ID }, + SPACE_1: { spaceId: SPACE_1_ID }, + SPACE_2: { spaceId: SPACE_2_ID }, +} = SPACES; + +const createTestCases = (crossSpaceSearch?: string[]) => { const cases = getTestCases({ crossSpaceSearch }); const normalTypes = [ cases.singleNamespaceType, cases.multiNamespaceType, cases.namespaceAgnosticType, + cases.eachType, cases.pageBeyondTotal, cases.unknownSearchField, cases.filterWithNamespaceAgnosticType, @@ -37,46 +46,35 @@ export default function ({ getService }: FtrProviderContext) { const { addTests, createTestDefinitions } = findTestSuiteFactory(esArchiver, supertest); const createTests = (user: TestUser) => { - const defaultCases = createTestCases([]); - const crossSpaceCases = createTestCases(['default', 'space_1', 'space_2']); + const defaultCases = createTestCases(); + const crossSpaceCases = createTestCases([DEFAULT_SPACE_ID, SPACE_1_ID, SPACE_2_ID]); - if (user.username === 'elastic') { + if (user.username === AUTHENTICATION.SUPERUSER.username) { return { defaultCases: createTestDefinitions(defaultCases.allTypes, false, { user }), crossSpace: createTestDefinitions( crossSpaceCases.allTypes, - { - statusCode: 400, - reason: 'cross_namespace_not_permitted', - }, + { statusCode: 400, reason: 'cross_namespace_not_permitted' }, { user } ), }; } - const authorizedGlobally = user.authorizedAtSpaces.includes('*'); + const isAuthorizedGlobally = user.authorizedAtSpaces.includes('*'); return { - defaultCases: authorizedGlobally + defaultCases: isAuthorizedGlobally ? [ - createTestDefinitions(defaultCases.normalTypes, false, { - user, - }), + createTestDefinitions(defaultCases.normalTypes, false, { user }), createTestDefinitions(defaultCases.hiddenAndUnknownTypes, { - statusCode: 403, - reason: 'forbidden_types', + statusCode: 200, + reason: 'unauthorized', }), ].flat() - : createTestDefinitions(defaultCases.allTypes, { - statusCode: 403, - reason: 'forbidden_types', - }), + : createTestDefinitions(defaultCases.allTypes, { statusCode: 200, reason: 'unauthorized' }), crossSpace: createTestDefinitions( crossSpaceCases.allTypes, - { - statusCode: 400, - reason: 'cross_namespace_not_permitted', - }, + { statusCode: 400, reason: 'cross_namespace_not_permitted' }, { user } ), }; diff --git a/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_create.ts b/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_create.ts index 74fade39bf7a5..ef47b09eddbc8 100644 --- a/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_create.ts +++ b/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_create.ts @@ -19,36 +19,48 @@ const { fail400, fail409 } = testCaseFailures; const unresolvableConflict = (condition?: boolean) => condition !== false ? { fail409Param: 'unresolvableConflict' } : {}; -const createTestCases = (overwrite: boolean, spaceId: string) => [ +const createTestCases = (overwrite: boolean, spaceId: string) => { // for each outcome, if failure !== undefined then we expect to receive // an error; otherwise, we expect to receive a success result - { - ...CASES.SINGLE_NAMESPACE_DEFAULT_SPACE, - ...fail409(!overwrite && spaceId === DEFAULT_SPACE_ID), - }, - { ...CASES.SINGLE_NAMESPACE_SPACE_1, ...fail409(!overwrite && spaceId === SPACE_1_ID) }, - { ...CASES.SINGLE_NAMESPACE_SPACE_2, ...fail409(!overwrite && spaceId === SPACE_2_ID) }, - { - ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, - ...fail409(!overwrite || (spaceId !== DEFAULT_SPACE_ID && spaceId !== SPACE_1_ID)), - ...unresolvableConflict(spaceId !== DEFAULT_SPACE_ID && spaceId !== SPACE_1_ID), - }, - { - ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, - ...fail409(!overwrite || spaceId !== SPACE_1_ID), - ...unresolvableConflict(spaceId !== SPACE_1_ID), - }, - { - ...CASES.MULTI_NAMESPACE_ONLY_SPACE_2, - ...fail409(!overwrite || spaceId !== SPACE_2_ID), - ...unresolvableConflict(spaceId !== SPACE_2_ID), - }, - { ...CASES.NAMESPACE_AGNOSTIC, ...fail409(!overwrite) }, - { ...CASES.HIDDEN, ...fail400() }, - CASES.NEW_SINGLE_NAMESPACE_OBJ, - CASES.NEW_MULTI_NAMESPACE_OBJ, - CASES.NEW_NAMESPACE_AGNOSTIC_OBJ, -]; + const expectedNamespaces = [spaceId]; // newly created objects should have this `namespaces` array in their return value + return [ + { + ...CASES.SINGLE_NAMESPACE_DEFAULT_SPACE, + ...fail409(!overwrite && spaceId === DEFAULT_SPACE_ID), + expectedNamespaces, + }, + { + ...CASES.SINGLE_NAMESPACE_SPACE_1, + ...fail409(!overwrite && spaceId === SPACE_1_ID), + expectedNamespaces, + }, + { + ...CASES.SINGLE_NAMESPACE_SPACE_2, + ...fail409(!overwrite && spaceId === SPACE_2_ID), + expectedNamespaces, + }, + { + ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, + ...fail409(!overwrite || (spaceId !== DEFAULT_SPACE_ID && spaceId !== SPACE_1_ID)), + ...unresolvableConflict(spaceId !== DEFAULT_SPACE_ID && spaceId !== SPACE_1_ID), + }, + { + ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, + ...fail409(!overwrite || spaceId !== SPACE_1_ID), + ...unresolvableConflict(spaceId !== SPACE_1_ID), + }, + { + ...CASES.MULTI_NAMESPACE_ONLY_SPACE_2, + ...fail409(!overwrite || spaceId !== SPACE_2_ID), + ...unresolvableConflict(spaceId !== SPACE_2_ID), + }, + { ...CASES.NAMESPACE_AGNOSTIC, ...fail409(!overwrite) }, + { ...CASES.HIDDEN, ...fail400() }, + { ...CASES.NEW_SINGLE_NAMESPACE_OBJ, expectedNamespaces }, + { ...CASES.NEW_MULTI_NAMESPACE_OBJ, expectedNamespaces }, + CASES.NEW_NAMESPACE_AGNOSTIC_OBJ, + ]; +}; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); diff --git a/x-pack/test/saved_object_api_integration/spaces_only/apis/create.ts b/x-pack/test/saved_object_api_integration/spaces_only/apis/create.ts index 1040f7fd81dde..10e57b4db82dc 100644 --- a/x-pack/test/saved_object_api_integration/spaces_only/apis/create.ts +++ b/x-pack/test/saved_object_api_integration/spaces_only/apis/create.ts @@ -16,27 +16,39 @@ const { } = SPACES; const { fail400, fail409 } = testCaseFailures; -const createTestCases = (overwrite: boolean, spaceId: string) => [ +const createTestCases = (overwrite: boolean, spaceId: string) => { // for each outcome, if failure !== undefined then we expect to receive // an error; otherwise, we expect to receive a success result - { - ...CASES.SINGLE_NAMESPACE_DEFAULT_SPACE, - ...fail409(!overwrite && spaceId === DEFAULT_SPACE_ID), - }, - { ...CASES.SINGLE_NAMESPACE_SPACE_1, ...fail409(!overwrite && spaceId === SPACE_1_ID) }, - { ...CASES.SINGLE_NAMESPACE_SPACE_2, ...fail409(!overwrite && spaceId === SPACE_2_ID) }, - { - ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, - ...fail409(!overwrite || (spaceId !== DEFAULT_SPACE_ID && spaceId !== SPACE_1_ID)), - }, - { ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, ...fail409(!overwrite || spaceId !== SPACE_1_ID) }, - { ...CASES.MULTI_NAMESPACE_ONLY_SPACE_2, ...fail409(!overwrite || spaceId !== SPACE_2_ID) }, - { ...CASES.NAMESPACE_AGNOSTIC, ...fail409(!overwrite) }, - { ...CASES.HIDDEN, ...fail400() }, - CASES.NEW_SINGLE_NAMESPACE_OBJ, - CASES.NEW_MULTI_NAMESPACE_OBJ, - CASES.NEW_NAMESPACE_AGNOSTIC_OBJ, -]; + const expectedNamespaces = [spaceId]; // newly created objects should have this `namespaces` array in their return value + return [ + { + ...CASES.SINGLE_NAMESPACE_DEFAULT_SPACE, + ...fail409(!overwrite && spaceId === DEFAULT_SPACE_ID), + expectedNamespaces, + }, + { + ...CASES.SINGLE_NAMESPACE_SPACE_1, + ...fail409(!overwrite && spaceId === SPACE_1_ID), + expectedNamespaces, + }, + { + ...CASES.SINGLE_NAMESPACE_SPACE_2, + ...fail409(!overwrite && spaceId === SPACE_2_ID), + expectedNamespaces, + }, + { + ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, + ...fail409(!overwrite || (spaceId !== DEFAULT_SPACE_ID && spaceId !== SPACE_1_ID)), + }, + { ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, ...fail409(!overwrite || spaceId !== SPACE_1_ID) }, + { ...CASES.MULTI_NAMESPACE_ONLY_SPACE_2, ...fail409(!overwrite || spaceId !== SPACE_2_ID) }, + { ...CASES.NAMESPACE_AGNOSTIC, ...fail409(!overwrite) }, + { ...CASES.HIDDEN, ...fail400() }, + { ...CASES.NEW_SINGLE_NAMESPACE_OBJ, expectedNamespaces }, + { ...CASES.NEW_MULTI_NAMESPACE_OBJ, expectedNamespaces }, + CASES.NEW_NAMESPACE_AGNOSTIC_OBJ, + ]; +}; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/saved_object_api_integration/spaces_only/apis/find.ts b/x-pack/test/saved_object_api_integration/spaces_only/apis/find.ts index 1d46985916cd5..c6779402d3291 100644 --- a/x-pack/test/saved_object_api_integration/spaces_only/apis/find.ts +++ b/x-pack/test/saved_object_api_integration/spaces_only/apis/find.ts @@ -4,11 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ +import { SPACES } from '../../common/lib/spaces'; import { getTestScenarios } from '../../common/lib/saved_object_test_utils'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { findTestSuiteFactory, getTestCases } from '../../common/suites/find'; -const createTestCases = (spaceId: string, crossSpaceSearch: string[]) => { +const { + DEFAULT: { spaceId: DEFAULT_SPACE_ID }, + SPACE_1: { spaceId: SPACE_1_ID }, + SPACE_2: { spaceId: SPACE_2_ID }, +} = SPACES; + +const createTestCases = (spaceId: string, crossSpaceSearch?: string[]) => { const cases = getTestCases({ currentSpace: spaceId, crossSpaceSearch }); return Object.values(cases); }; @@ -18,15 +25,19 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const { addTests, createTestDefinitions } = findTestSuiteFactory(esArchiver, supertest); - const createTests = (spaceId: string, crossSpaceSearch: string[]) => { + const createTests = (spaceId: string, crossSpaceSearch?: string[]) => { const testCases = createTestCases(spaceId, crossSpaceSearch); return createTestDefinitions(testCases, false); }; describe('_find', () => { getTestScenarios().spaces.forEach(({ spaceId }) => { - const currentSpaceTests = createTests(spaceId, []); - const explicitCrossSpaceTests = createTests(spaceId, ['default', 'space_1', 'space_2']); + const currentSpaceTests = createTests(spaceId); + const explicitCrossSpaceTests = createTests(spaceId, [ + DEFAULT_SPACE_ID, + SPACE_1_ID, + SPACE_2_ID, + ]); const wildcardCrossSpaceTests = createTests(spaceId, ['*']); addTests(`within the ${spaceId} space`, { spaceId, diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts index 4beb64affc46b..c9d2b7a21d0da 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts @@ -65,7 +65,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { ], ]; - describe('endpoint list', function () { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/72102 + describe.skip('endpoint list', function () { this.tags('ciGroup7'); const sleep = (ms = 100) => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/resolver.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/resolver.ts index 620eab37f9b46..3e9726bf40073 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/resolver.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/resolver.ts @@ -13,7 +13,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const queryBar = getService('queryBar'); - describe('Endpoint Event Resolver', function () { + // FLAKY: https://github.com/elastic/kibana/issues/78375 + describe.skip('Endpoint Event Resolver', function () { before(async () => { await esArchiver.load('endpoint/resolver_tree', { useCreate: true }); await pageObjects.hosts.navigateToSecurityHostsPage(); diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/artifacts/index.ts b/x-pack/test/security_solution_endpoint_api_int/apis/artifacts/index.ts index 17a4182fe9371..5a4053ee6f0a9 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/artifacts/index.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/artifacts/index.ts @@ -18,7 +18,8 @@ export default function (providerContext: FtrProviderContext) { const supertestWithoutAuth = getSupertestWithoutAuth(providerContext); let agentAccessAPIKey: string; - describe('artifact download', () => { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/72102 + describe.skip('artifact download', () => { before(async () => { await esArchiver.load('endpoint/artifacts/api_feature', { useCreate: true }); diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts index b157c3159ccc0..2ab12e1ff3aae 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts @@ -23,7 +23,8 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); - describe('test metadata api', () => { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/72102 + describe.skip('test metadata api', () => { describe(`POST ${METADATA_REQUEST_ROUTE} when index is empty`, () => { it('metadata api should return empty result when index is empty', async () => { await deleteMetadataStream(getService); diff --git a/x-pack/tsconfig.json b/x-pack/tsconfig.json index 44c8449dc5dd0..7978a89231566 100644 --- a/x-pack/tsconfig.json +++ b/x-pack/tsconfig.json @@ -3,9 +3,6 @@ "include": [ "mocks.ts", "typings/**/*", - "legacy/common/**/*", - "legacy/server/**/*", - "legacy/plugins/**/*", "plugins/**/*", "test_utils/**/*", "tasks/**/*" @@ -21,7 +18,6 @@ "paths": { "kibana/public": ["src/core/public"], "kibana/server": ["src/core/server"], - "plugins/xpack_main/*": ["x-pack/legacy/plugins/xpack_main/public/*"], "test_utils/*": ["x-pack/test_utils/*"], "fixtures/*": ["src/fixtures/*"] }, diff --git a/x-pack/typings/hapi.d.ts b/x-pack/typings/hapi.d.ts index 253b639a52ff2..dd9e0239aeee7 100644 --- a/x-pack/typings/hapi.d.ts +++ b/x-pack/typings/hapi.d.ts @@ -6,7 +6,6 @@ import 'hapi'; -import { XPackMainPlugin } from '../legacy/plugins/xpack_main/server/xpack_main'; import { ActionsPlugin, ActionsClient } from '../plugins/actions/server'; import { AlertingPlugin, AlertsClient } from '../plugins/alerts/server'; import { TaskManager } from '../plugins/task_manager/server'; @@ -17,7 +16,6 @@ declare module 'hapi' { getAlertsClient?: () => AlertsClient; } interface PluginProperties { - xpack_main: XPackMainPlugin; actions?: ActionsPlugin; alerts?: AlertingPlugin; task_manager?: TaskManager; diff --git a/yarn.lock b/yarn.lock index 98a9fbfd9d834..1659dcdd33ff1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -982,7 +982,7 @@ pirates "^4.0.0" source-map-support "^0.5.16" -"@babel/runtime-corejs2@^7.2.0", "@babel/runtime-corejs2@^7.6.3": +"@babel/runtime-corejs2@^7.2.0": version "7.11.2" resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.11.2.tgz#700a03945ebad0d31ba6690fc8a6bcc9040faa47" integrity sha512-AC/ciV28adSSpEkBglONBWq4/Lvm6GAZuxIoyVtsnUpZMl0bxLtoChEnYAkP+47KyOCayZanojtflUEUJtR/6Q== @@ -1129,12 +1129,21 @@ opentracing "^0.14.3" promise-polyfill "^8.1.3" -"@elastic/apm-rum-react@^1.2.3": - version "1.2.3" - resolved "https://registry.yarnpkg.com/@elastic/apm-rum-react/-/apm-rum-react-1.2.3.tgz#fdf28492daca0ee6aa67c53a457eea1f16739e1e" - integrity sha512-oCjF/L46OYDRLHKt60l7aU+DFE484dwb/kKN12VZCOgueDZm4BCJd7yaosBtWDhnw0tl0Iqc0X3r4U7pQ+g9aA== +"@elastic/apm-rum-core@^5.6.1": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@elastic/apm-rum-core/-/apm-rum-core-5.6.1.tgz#0870e654e84e1f2ffea7c8a247a2da1b72918bcd" + integrity sha512-UtWj8UNN1sfSjav1kQK2NFhHtrH++4FzhtY0g80aSfHrDdBKVXaecWswoGmK3aiGJ9LAVlAXNfF3tPMT6JN23g== + dependencies: + error-stack-parser "^1.3.5" + opentracing "^0.14.3" + promise-polyfill "^8.1.3" + +"@elastic/apm-rum-react@^1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@elastic/apm-rum-react/-/apm-rum-react-1.2.4.tgz#f5b908f69f2696af10d19250226559ceb33dc1e9" + integrity sha512-zjig55n4/maU+kAEePS+DxgD12t4J0X9t3tB9YuO0gUIJhgT7KTL1Nv93ZmJ3u2tCJSpdYVfKQ0GBgSfjt1vVQ== dependencies: - "@elastic/apm-rum" "^5.5.0" + "@elastic/apm-rum" "^5.6.0" hoist-non-react-statics "^3.3.0" "@elastic/apm-rum@^5.5.0": @@ -1144,6 +1153,13 @@ dependencies: "@elastic/apm-rum-core" "^5.6.0" +"@elastic/apm-rum@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@elastic/apm-rum/-/apm-rum-5.6.0.tgz#0af2acb55091b9eb315cf38c6422a83cddfecb6f" + integrity sha512-6CuODbt7dBXoqsKoqhshQQC4GyqsGMPOR1FXZCWbnq55UZq1TWqra6zNCtEEFinz8rPaww7bzmNciXKRvGjIzQ== + dependencies: + "@elastic/apm-rum-core" "^5.6.1" + "@elastic/charts@21.1.2": version "21.1.2" resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-21.1.2.tgz#da7e9c1025bf730a738b6ac6d7024d97dd2b5aa2" @@ -2458,6 +2474,25 @@ jsonwebtoken "^8.3.0" lru-cache "^5.1.1" +"@octokit/auth-token@^2.4.0": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.2.tgz#10d0ae979b100fa6b72fa0e8e63e27e6d0dbff8a" + integrity sha512-jE/lE/IKIz2v1+/P0u4fJqv0kYwXOTujKemJMFr6FeopsxlIK3+wKDCJGnysg81XID5TgZQbIfuJ5J0lnTiuyQ== + dependencies: + "@octokit/types" "^5.0.0" + +"@octokit/core@^3.0.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.1.2.tgz#c937d5f9621b764573068fcd2e5defcc872fd9cc" + integrity sha512-AInOFULmwOa7+NFi9F8DlDkm5qtZVmDQayi7TUgChE3yeIGPq0Y+6cAEXPexQ3Ea+uZy66hKEazR7DJyU+4wfw== + dependencies: + "@octokit/auth-token" "^2.4.0" + "@octokit/graphql" "^4.3.1" + "@octokit/request" "^5.4.0" + "@octokit/types" "^5.0.0" + before-after-hook "^2.1.0" + universal-user-agent "^6.0.0" + "@octokit/endpoint@^3.2.0": version "3.2.3" resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-3.2.3.tgz#bd9aea60cd94ce336656b57a5c9cb7f10be8f4f3" @@ -2468,6 +2503,44 @@ universal-user-agent "^2.0.1" url-template "^2.0.8" +"@octokit/endpoint@^6.0.1": + version "6.0.6" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.6.tgz#4f09f2b468976b444742a1d5069f6fa45826d999" + integrity sha512-7Cc8olaCoL/mtquB7j/HTbPM+sY6Ebr4k2X2y4JoXpVKQ7r5xB4iGQE0IoO58wIPsUk4AzoT65AMEpymSbWTgQ== + dependencies: + "@octokit/types" "^5.0.0" + is-plain-object "^5.0.0" + universal-user-agent "^6.0.0" + +"@octokit/graphql@^4.3.1": + version "4.5.6" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.5.6.tgz#708143ba15cf7c1879ed6188266e7f270be805d4" + integrity sha512-Rry+unqKTa3svswT2ZAuqenpLrzJd+JTv89LTeVa5UM/5OX8o4KTkPL7/1ABq4f/ZkELb0XEK/2IEoYwykcLXg== + dependencies: + "@octokit/request" "^5.3.0" + "@octokit/types" "^5.0.0" + universal-user-agent "^6.0.0" + +"@octokit/plugin-paginate-rest@^2.2.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.4.0.tgz#92f951ddc8a1cd505353fa07650752ca25ed7e93" + integrity sha512-YT6Klz3LLH6/nNgi0pheJnUmTFW4kVnxGft+v8Itc41IIcjl7y1C8TatmKQBbCSuTSNFXO5pCENnqg6sjwpJhg== + dependencies: + "@octokit/types" "^5.5.0" + +"@octokit/plugin-request-log@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.0.tgz#eef87a431300f6148c39a7f75f8cfeb218b2547e" + integrity sha512-ywoxP68aOT3zHCLgWZgwUJatiENeHE7xJzYjfz8WI0goynp96wETBF+d95b8g/uL4QmS6owPVlaxiz3wyMAzcw== + +"@octokit/plugin-rest-endpoint-methods@4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.2.0.tgz#c5a0691b3aba5d8b4ef5dffd6af3649608f167ba" + integrity sha512-1/qn1q1C1hGz6W/iEDm9DoyNoG/xdFDt78E3eZ5hHeUfJTLJgyAMdj9chL/cNBHjcjd+FH5aO1x0VCqR2RE0mw== + dependencies: + "@octokit/types" "^5.5.0" + deprecation "^2.3.1" + "@octokit/plugin-retry@^2.2.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@octokit/plugin-retry/-/plugin-retry-2.2.0.tgz#11f3957a46ccdb7b7f33caabf8c17e57b25b80b2" @@ -2475,6 +2548,15 @@ dependencies: bottleneck "^2.15.3" +"@octokit/request-error@^2.0.0": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.0.2.tgz#0e76b83f5d8fdda1db99027ea5f617c2e6ba9ed0" + integrity sha512-2BrmnvVSV1MXQvEkrb9zwzP0wXFNbPJij922kYBTLIlIafukrGOb+ABBT2+c6wZiuyWDH1K1zmjGQ0toN/wMWw== + dependencies: + "@octokit/types" "^5.0.1" + deprecation "^2.0.0" + once "^1.4.0" + "@octokit/request@2.4.2", "@octokit/request@^2.1.2", "@octokit/request@^2.4.2": version "2.4.2" resolved "https://registry.yarnpkg.com/@octokit/request/-/request-2.4.2.tgz#87c36e820dd1e43b1629f4f35c95b00cd456320b" @@ -2487,6 +2569,20 @@ once "^1.4.0" universal-user-agent "^2.0.1" +"@octokit/request@^5.3.0", "@octokit/request@^5.4.0": + version "5.4.9" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.9.tgz#0a46f11b82351b3416d3157261ad9b1558c43365" + integrity sha512-CzwVvRyimIM1h2n9pLVYfTDmX9m+KHSgCpqPsY8F1NdEK8IaWqXhSBXsdjOBFZSpEcxNEeg4p0UO9cQ8EnOCLA== + dependencies: + "@octokit/endpoint" "^6.0.1" + "@octokit/request-error" "^2.0.0" + "@octokit/types" "^5.0.0" + deprecation "^2.0.0" + is-plain-object "^5.0.0" + node-fetch "^2.6.1" + once "^1.4.0" + universal-user-agent "^6.0.0" + "@octokit/rest@^16.23.2": version "16.23.2" resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-16.23.2.tgz#975e84610427c4ab6c41bec77c24aed9b7563db4" @@ -2505,6 +2601,23 @@ universal-user-agent "^2.0.0" url-template "^2.0.8" +"@octokit/rest@^18.0.6": + version "18.0.6" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.0.6.tgz#76c274f1a68f40741a131768ef483f041e7b98b6" + integrity sha512-ES4lZBKPJMX/yUoQjAZiyFjei9pJ4lTTfb9k7OtYoUzKPDLl/M8jiHqt6qeSauyU4eZGLw0sgP1WiQl9FYeM5w== + dependencies: + "@octokit/core" "^3.0.0" + "@octokit/plugin-paginate-rest" "^2.2.0" + "@octokit/plugin-request-log" "^1.0.0" + "@octokit/plugin-rest-endpoint-methods" "4.2.0" + +"@octokit/types@^5.0.0", "@octokit/types@^5.0.1", "@octokit/types@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-5.5.0.tgz#e5f06e8db21246ca102aa28444cdb13ae17a139b" + integrity sha512-UZ1pErDue6bZNjYOotCNveTXArOMZQFG6hKJfOnGnulVCMcVVi7YIIuuR4WfBhjo7zgpmzn/BkPDnUXtNx+PcQ== + dependencies: + "@types/node" ">= 8" + "@percy/agent@^0.26.0": version "0.26.0" resolved "https://registry.yarnpkg.com/@percy/agent/-/agent-0.26.0.tgz#9f06849d752df7368198835d0b5edc16c2d69a0c" @@ -4112,16 +4225,30 @@ "@types/node" "*" "@types/webpack" "*" -"@types/lodash@^4.14.159": - version "4.14.159" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.159.tgz#61089719dc6fdd9c5cb46efc827f2571d1517065" - integrity sha512-gF7A72f7WQN33DpqOWw9geApQPh4M3PxluMtaHxWHXEGSN12/WbcEk/eNSqWNQcQhF66VSZ06vCF94CrHwXJDg== +"@types/lodash.difference@^4.5.6": + version "4.5.6" + resolved "https://registry.yarnpkg.com/@types/lodash.difference/-/lodash.difference-4.5.6.tgz#41ec5c4e684eeacf543848a9a1b2a4856ccf9853" + integrity sha512-wXH53r+uoUCrKhmh7S5Gf6zo3vpsx/zH2R4pvkmDlmopmMTCROAUXDpPMXATGCWkCjE6ik3VZzZUxBgMjZho9Q== + dependencies: + "@types/lodash" "*" + +"@types/lodash.intersection@^4.4.6": + version "4.4.6" + resolved "https://registry.yarnpkg.com/@types/lodash.intersection/-/lodash.intersection-4.4.6.tgz#0fb241badf6edbb2a7d194a70c50e950e2486e68" + integrity sha512-6ewsKax7+HgT+7mEhzXT6tIyIHc/mjCwZJnarvLbCrtW21qmDQHWbaJj4Ht4DQDBmMdnvZe8APuVlsMpZ5E5mQ== + dependencies: + "@types/lodash" "*" -"@types/lodash@^4.14.160": +"@types/lodash@*", "@types/lodash@^4.14.160": version "4.14.161" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.161.tgz#a21ca0777dabc6e4f44f3d07f37b765f54188b18" integrity sha512-EP6O3Jkr7bXvZZSZYlsgt5DIjiGr0dXP1/jVEwVLTFgg0d+3lWVQkRavYVQszV7dYUwvg0B8R0MBDpcmXg7XIA== +"@types/lodash@^4.14.159": + version "4.14.159" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.159.tgz#61089719dc6fdd9c5cb46efc827f2571d1517065" + integrity sha512-gF7A72f7WQN33DpqOWw9geApQPh4M3PxluMtaHxWHXEGSN12/WbcEk/eNSqWNQcQhF66VSZ06vCF94CrHwXJDg== + "@types/log-symbols@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/log-symbols/-/log-symbols-2.0.0.tgz#7919e2ec3c8d13879bfdcab310dd7a3f7fc9466d" @@ -4269,7 +4396,7 @@ dependencies: "@types/node" "*" -"@types/node@*", "@types/node@8.10.54", "@types/node@>=10.17.17 <10.20.0", "@types/node@>=8.9.0", "@types/node@^12.0.2": +"@types/node@*", "@types/node@8.10.54", "@types/node@>= 8", "@types/node@>=10.17.17 <10.20.0", "@types/node@>=8.9.0", "@types/node@^12.0.2": version "10.17.26" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.26.tgz#a8a119960bff16b823be4c617da028570779bcfd" integrity sha512-myMwkO2Cr82kirHY8uknNRHEVtn0wV3DTQfkrjx17jmkstDRZ24gNUdl8AHXVyVclTYI/bNjgTPTAWvWLqXqkw== @@ -4423,13 +4550,6 @@ "@types/history" "*" "@types/react" "*" -"@types/react-beautiful-dnd@^12.1.1": - version "12.1.1" - resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-12.1.1.tgz#149e638c0f912eee6b74ea419b26bb43d0b1da60" - integrity sha512-CPKynKgGVRK+xmywLMD0qNWamdscxhgf1Um+2oEgN6Qibn1rye3M4p2bdxAMgtOTZ2L81bYl6KGKSzJVboJWeA== - dependencies: - "@types/react" "*" - "@types/react-beautiful-dnd@^13.0.0": version "13.0.0" resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz#e60d3d965312fcf1516894af92dc3e9249587db4" @@ -7213,26 +7333,31 @@ bach@^1.0.0: async-settle "^1.0.0" now-and-later "^2.0.0" -backport@5.5.1: - version "5.5.1" - resolved "https://registry.yarnpkg.com/backport/-/backport-5.5.1.tgz#2eeddbdc4cfc0530119bdb2b0c3c30bc7ef574dd" - integrity sha512-vQuGrxxMx9H64ywqsIYUHL8+/xvPeP0nnBa0YQt5S+XqW7etaqOoa5dFW0c77ADdqjfLlGUIvtc2i6UrmqeFUQ== +backport@5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/backport/-/backport-5.6.0.tgz#6dcc0485e5eecf66bb6f950983fd0b018217ec20" + integrity sha512-wz7Ve3uslhGUMtHuctqIEtZFItTGKRRMiNANYso0iw1ar81ILsczDGgxeOlzmmnIQFi1ZvEs6lX3cgypGfef9A== dependencies: - axios "^0.19.2" + "@octokit/rest" "^18.0.6" + "@types/lodash.difference" "^4.5.6" + "@types/lodash.intersection" "^4.4.6" + axios "^0.19.0" dedent "^0.7.0" del "^5.1.0" - find-up "^4.1.0" - inquirer "^7.3.1" + find-up "^5.0.0" + inquirer "^7.3.3" + lodash.difference "^4.5.0" lodash.flatmap "^4.5.0" + lodash.intersection "^4.4.0" lodash.isempty "^4.4.0" lodash.isstring "^4.0.1" lodash.uniq "^4.5.0" make-dir "^3.1.0" - ora "^4.0.4" + ora "^5.1.0" safe-json-stringify "^1.2.0" - strip-json-comments "^3.1.0" + strip-json-comments "^3.1.1" winston "^3.3.3" - yargs "^15.4.0" + yargs "^16.0.3" bail@^1.0.0: version "1.0.2" @@ -7306,6 +7431,11 @@ before-after-hook@^1.4.0: resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-1.4.0.tgz#2b6bf23dca4f32e628fd2747c10a37c74a4b484d" integrity sha512-l5r9ir56nda3qu14nAXIlyq1MmUSs0meCIaFAh8HwkFwP1F8eToOuS3ah2VAHHcY04jaYD7FpJC5JTXHYRbkzg== +before-after-hook@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635" + integrity sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A== + big-integer@^1.6.16: version "1.6.48" resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" @@ -8622,10 +8752,10 @@ cli-spinners@^2.0.0: resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.1.0.tgz#22c34b4d51f573240885b201efda4e4ec9fff3c7" integrity sha512-8B00fJOEh1HPrx4fo5eW16XmE1PcL1tGpGrxy63CXGP9nHdPBN63X75hA1zhvQuhVztJWLqV58Roj2qlNM7cAA== -cli-spinners@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.2.0.tgz#e8b988d9206c692302d8ee834e7a85c0144d8f77" - integrity sha512-tgU3fKwzYjiLEQgPMD9Jt+JjHVL9kW93FiIMX/l7rivvOD4/LL0Mf7gda3+4U2KJBloybwgj5KEoQgGRioMiKQ== +cli-spinners@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.4.0.tgz#c6256db216b878cfba4720e719cec7cf72685d7f" + integrity sha512-sJAofoarcm76ZGpuooaO0eDy8saEy+YoZBLjC4h8srt4jeBnkYeOgqxgsJQTpyt2LjI5PTfLJHSL+41Yu4fEJA== cli-table3@0.5.1: version "0.5.1" @@ -8744,6 +8874,15 @@ cliui@^6.0.0: strip-ansi "^6.0.0" wrap-ansi "^6.2.0" +cliui@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.1.tgz#a4cb67aad45cd83d8d05128fc9f4d8fbb887e6b3" + integrity sha512-rcvHOWyGyid6I1WjT/3NatKj2kDt9OdSHSXpyLXaMWFbKpGACNW8pRhhdPUq9MWUOdwn8Rz9AVETjF4105rZZQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + clone-buffer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" @@ -10639,6 +10778,11 @@ deprecation@^1.0.1: resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-1.0.1.tgz#2df79b79005752180816b7b6e079cbd80490d711" integrity sha512-ccVHpE72+tcIKaGMql33x5MAjKQIZrk+3x2GbJ7TeraUCZWHoT+KSZpoC+JQFsUBlSTXUrBaGiF0j6zVTepPLg== +deprecation@^2.0.0, deprecation@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" + integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== + des.js@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" @@ -11755,6 +11899,11 @@ es6-weak-map@^2.0.1, es6-weak-map@^2.0.2: es6-iterator "^2.0.1" es6-symbol "^3.1.1" +escalade@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.0.tgz#e8e2d7c7a8b76f6ee64c2181d6b8151441602d4e" + integrity sha512-mAk+hPSO8fLDkhV7V0dXazH5pDc6MrjBTPyD3VeKzxnVFjH1MIxbCdqGZB9O8+EwWakZs3ZCbDS4IpRt79V1ig== + escape-goat@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" @@ -13059,6 +13208,14 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + find-versions@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-2.0.0.tgz#2ad90d490f6828c1aa40292cf709ac3318210c3c" @@ -13694,7 +13851,7 @@ get-caller-file@^1.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== -get-caller-file@^2.0.1: +get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== @@ -16006,7 +16163,7 @@ inquirer@^6.0.0: strip-ansi "^5.1.0" through "^2.3.6" -inquirer@^7.0.0, inquirer@^7.3.1, inquirer@^7.3.3: +inquirer@^7.0.0, inquirer@^7.3.3: version "7.3.3" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== @@ -16677,6 +16834,11 @@ is-plain-object@3.0.0, is-plain-object@^3.0.0: dependencies: isobject "^4.0.0" +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + is-promise@^2.1, is-promise@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" @@ -18711,6 +18873,13 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + locutus@^2.0.5: version "2.0.10" resolved "https://registry.yarnpkg.com/locutus/-/locutus-2.0.10.tgz#f903619466a98a4ab76e8b87a5854b55a743b917" @@ -19011,7 +19180,7 @@ log-symbols@2.2.0, log-symbols@^2.0.0, log-symbols@^2.1.0, log-symbols@^2.2.0: dependencies: chalk "^2.0.1" -log-symbols@3.0.0, log-symbols@^3.0.0: +log-symbols@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== @@ -21460,16 +21629,16 @@ ora@^3.0.0: strip-ansi "^5.2.0" wcwidth "^1.0.1" -ora@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/ora/-/ora-4.0.4.tgz#e8da697cc5b6a47266655bf68e0fb588d29a545d" - integrity sha512-77iGeVU1cIdRhgFzCK8aw1fbtT1B/iZAvWjS+l/o1x0RShMgxHUZaD2yDpWsNCPwXg9z1ZA78Kbdvr8kBmG/Ww== +ora@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.1.0.tgz#b188cf8cd2d4d9b13fd25383bc3e5cba352c94f8" + integrity sha512-9tXIMPvjZ7hPTbk8DFq1f7Kow/HU/pQYB60JbNq+QnGwcyhWVZaQ4hM9zQDEsPxw/muLpgiHSaumUZxCAmod/w== dependencies: - chalk "^3.0.0" + chalk "^4.1.0" cli-cursor "^3.1.0" - cli-spinners "^2.2.0" + cli-spinners "^2.4.0" is-interactive "^1.0.0" - log-symbols "^3.0.0" + log-symbols "^4.0.0" mute-stream "0.0.8" strip-ansi "^6.0.0" wcwidth "^1.0.1" @@ -21640,6 +21809,13 @@ p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.3.0: dependencies: p-try "^2.0.0" +p-limit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.0.2.tgz#1664e010af3cadc681baafd3e2a437be7b0fb5fe" + integrity sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg== + dependencies: + p-try "^2.0.0" + p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" @@ -21661,6 +21837,13 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + p-map@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" @@ -23289,19 +23472,6 @@ react-apollo@^2.1.4: lodash "^4.17.10" prop-types "^15.6.0" -react-beautiful-dnd@^12.2.0: - version "12.2.0" - resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-12.2.0.tgz#e5f6222f9e7934c6ed4ee09024547f9e353ae423" - integrity sha512-s5UrOXNDgeEC+sx65IgbeFlqKKgK3c0UfbrJLWufP34WBheyu5kJ741DtJbsSgPKyNLkqfswpMYr0P8lRj42cA== - dependencies: - "@babel/runtime-corejs2" "^7.6.3" - css-box-model "^1.2.0" - memoize-one "^5.1.1" - raf-schd "^4.0.2" - react-redux "^7.1.1" - redux "^4.0.4" - use-memo-one "^1.1.1" - react-beautiful-dnd@^13.0.0: version "13.0.0" resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz#f70cc8ff82b84bc718f8af157c9f95757a6c3b40" @@ -26966,10 +27136,10 @@ strip-json-comments@^3.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7" integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw== -strip-json-comments@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180" - integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w== +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== strip-json-comments@~1.0.1: version "1.0.4" @@ -28707,6 +28877,11 @@ universal-user-agent@^2.0.0, universal-user-agent@^2.0.1: dependencies: os-name "^3.0.0" +universal-user-agent@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" + integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== + universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -30291,6 +30466,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -30540,6 +30724,11 @@ y18n@^4.0.0: resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== +y18n@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.1.tgz#1ad2a7eddfa8bce7caa2e1f6b5da96c39d99d571" + integrity sha512-/jJ831jEs4vGDbYPQp4yGKDYPSCCEQ45uZWJHE1AoYBzqdZi8+LDWas0z4HrmJXmKdpFsTiowSHXdxyFhpmdMg== + yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" @@ -30601,6 +30790,11 @@ yargs-parser@^18.1.1, yargs-parser@^18.1.2, yargs-parser@^18.1.3: camelcase "^5.0.0" decamelize "^1.2.0" +yargs-parser@^20.0.0: + version "20.2.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.0.tgz#944791ca2be2e08ddadd3d87e9de4c6484338605" + integrity sha512-2agPoRFPoIcFzOIp6656gcvsg2ohtscpw2OINr/q46+Sq41xz2OYLqx5HRHabmFU1OARIPAYH5uteICE7mn/5A== + yargs-unparser@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f" @@ -30643,7 +30837,7 @@ yargs@13.3.2, yargs@^13.2.2, yargs@^13.3.0, yargs@^13.3.2: y18n "^4.0.0" yargs-parser "^13.1.2" -yargs@^15.0.2, yargs@^15.1.0, yargs@^15.3.1, yargs@^15.4.0, yargs@^15.4.1: +yargs@^15.0.2, yargs@^15.1.0, yargs@^15.3.1, yargs@^15.4.1: version "15.4.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== @@ -30660,6 +30854,19 @@ yargs@^15.0.2, yargs@^15.1.0, yargs@^15.3.1, yargs@^15.4.0, yargs@^15.4.1: y18n "^4.0.0" yargs-parser "^18.1.2" +yargs@^16.0.3: + version "16.0.3" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.0.3.tgz#7a919b9e43c90f80d4a142a89795e85399a7e54c" + integrity sha512-6+nLw8xa9uK1BOEOykaiYAJVh6/CjxWXK/q9b5FpRgNslt8s22F2xMBqVIKgCRjNgGvGPBy8Vog7WN7yh4amtA== + dependencies: + cliui "^7.0.0" + escalade "^3.0.2" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.1" + yargs-parser "^20.0.0" + yargs@^3.15.0: version "3.32.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995"